dcp-client 4.2.20 → 4.2.22
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.
|
@@ -12,6 +12,39 @@
|
|
|
12
12
|
/******/ (() => { // webpackBootstrap
|
|
13
13
|
/******/ var __webpack_modules__ = ({
|
|
14
14
|
|
|
15
|
+
/***/ "./node_modules/@selderee/plugin-htmlparser2/node_modules/domelementtype/lib/index.js":
|
|
16
|
+
/*!********************************************************************************************!*\
|
|
17
|
+
!*** ./node_modules/@selderee/plugin-htmlparser2/node_modules/domelementtype/lib/index.js ***!
|
|
18
|
+
\********************************************************************************************/
|
|
19
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
20
|
+
|
|
21
|
+
"use strict";
|
|
22
|
+
eval("\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.Doctype = exports.CDATA = exports.Tag = exports.Style = exports.Script = exports.Comment = exports.Directive = exports.Text = exports.Root = exports.isTag = exports.ElementType = void 0;\n/** Types of elements found in htmlparser2's DOM */\nvar ElementType;\n(function (ElementType) {\n /** Type for the root element of a document */\n ElementType[\"Root\"] = \"root\";\n /** Type for Text */\n ElementType[\"Text\"] = \"text\";\n /** Type for <? ... ?> */\n ElementType[\"Directive\"] = \"directive\";\n /** Type for <!-- ... --> */\n ElementType[\"Comment\"] = \"comment\";\n /** Type for <script> tags */\n ElementType[\"Script\"] = \"script\";\n /** Type for <style> tags */\n ElementType[\"Style\"] = \"style\";\n /** Type for Any tag */\n ElementType[\"Tag\"] = \"tag\";\n /** Type for <![CDATA[ ... ]]> */\n ElementType[\"CDATA\"] = \"cdata\";\n /** Type for <!doctype ...> */\n ElementType[\"Doctype\"] = \"doctype\";\n})(ElementType = exports.ElementType || (exports.ElementType = {}));\n/**\n * Tests whether an element is a tag or not.\n *\n * @param elem Element to test\n */\nfunction isTag(elem) {\n return (elem.type === ElementType.Tag ||\n elem.type === ElementType.Script ||\n elem.type === ElementType.Style);\n}\nexports.isTag = isTag;\n// Exports for backwards compatibility\n/** Type for the root element of a document */\nexports.Root = ElementType.Root;\n/** Type for Text */\nexports.Text = ElementType.Text;\n/** Type for <? ... ?> */\nexports.Directive = ElementType.Directive;\n/** Type for <!-- ... --> */\nexports.Comment = ElementType.Comment;\n/** Type for <script> tags */\nexports.Script = ElementType.Script;\n/** Type for <style> tags */\nexports.Style = ElementType.Style;\n/** Type for Any tag */\nexports.Tag = ElementType.Tag;\n/** Type for <![CDATA[ ... ]]> */\nexports.CDATA = ElementType.CDATA;\n/** Type for <!doctype ...> */\nexports.Doctype = ElementType.Doctype;\n\n\n//# sourceURL=webpack://dcp/./node_modules/@selderee/plugin-htmlparser2/node_modules/domelementtype/lib/index.js?");
|
|
23
|
+
|
|
24
|
+
/***/ }),
|
|
25
|
+
|
|
26
|
+
/***/ "./node_modules/@selderee/plugin-htmlparser2/node_modules/domhandler/lib/index.js":
|
|
27
|
+
/*!****************************************************************************************!*\
|
|
28
|
+
!*** ./node_modules/@selderee/plugin-htmlparser2/node_modules/domhandler/lib/index.js ***!
|
|
29
|
+
\****************************************************************************************/
|
|
30
|
+
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
31
|
+
|
|
32
|
+
"use strict";
|
|
33
|
+
eval("\nvar __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n var desc = Object.getOwnPropertyDescriptor(m, k);\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\n desc = { enumerable: true, get: function() { return m[k]; } };\n }\n Object.defineProperty(o, k2, desc);\n}) : (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n o[k2] = m[k];\n}));\nvar __exportStar = (this && this.__exportStar) || function(m, exports) {\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);\n};\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.DomHandler = void 0;\nvar domelementtype_1 = __webpack_require__(/*! domelementtype */ \"./node_modules/@selderee/plugin-htmlparser2/node_modules/domelementtype/lib/index.js\");\nvar node_1 = __webpack_require__(/*! ./node */ \"./node_modules/@selderee/plugin-htmlparser2/node_modules/domhandler/lib/node.js\");\n__exportStar(__webpack_require__(/*! ./node */ \"./node_modules/@selderee/plugin-htmlparser2/node_modules/domhandler/lib/node.js\"), exports);\nvar reWhitespace = /\\s+/g;\n// Default options\nvar defaultOpts = {\n normalizeWhitespace: false,\n withStartIndices: false,\n withEndIndices: false,\n xmlMode: false,\n};\nvar DomHandler = /** @class */ (function () {\n /**\n * @param callback Called once parsing has completed.\n * @param options Settings for the handler.\n * @param elementCB Callback whenever a tag is closed.\n */\n function DomHandler(callback, options, elementCB) {\n /** The elements of the DOM */\n this.dom = [];\n /** The root element for the DOM */\n this.root = new node_1.Document(this.dom);\n /** Indicated whether parsing has been completed. */\n this.done = false;\n /** Stack of open tags. */\n this.tagStack = [this.root];\n /** A data node that is still being written to. */\n this.lastNode = null;\n /** Reference to the parser instance. Used for location information. */\n this.parser = null;\n // Make it possible to skip arguments, for backwards-compatibility\n if (typeof options === \"function\") {\n elementCB = options;\n options = defaultOpts;\n }\n if (typeof callback === \"object\") {\n options = callback;\n callback = undefined;\n }\n this.callback = callback !== null && callback !== void 0 ? callback : null;\n this.options = options !== null && options !== void 0 ? options : defaultOpts;\n this.elementCB = elementCB !== null && elementCB !== void 0 ? elementCB : null;\n }\n DomHandler.prototype.onparserinit = function (parser) {\n this.parser = parser;\n };\n // Resets the handler back to starting state\n DomHandler.prototype.onreset = function () {\n this.dom = [];\n this.root = new node_1.Document(this.dom);\n this.done = false;\n this.tagStack = [this.root];\n this.lastNode = null;\n this.parser = null;\n };\n // Signals the handler that parsing is done\n DomHandler.prototype.onend = function () {\n if (this.done)\n return;\n this.done = true;\n this.parser = null;\n this.handleCallback(null);\n };\n DomHandler.prototype.onerror = function (error) {\n this.handleCallback(error);\n };\n DomHandler.prototype.onclosetag = function () {\n this.lastNode = null;\n var elem = this.tagStack.pop();\n if (this.options.withEndIndices) {\n elem.endIndex = this.parser.endIndex;\n }\n if (this.elementCB)\n this.elementCB(elem);\n };\n DomHandler.prototype.onopentag = function (name, attribs) {\n var type = this.options.xmlMode ? domelementtype_1.ElementType.Tag : undefined;\n var element = new node_1.Element(name, attribs, undefined, type);\n this.addNode(element);\n this.tagStack.push(element);\n };\n DomHandler.prototype.ontext = function (data) {\n var normalizeWhitespace = this.options.normalizeWhitespace;\n var lastNode = this.lastNode;\n if (lastNode && lastNode.type === domelementtype_1.ElementType.Text) {\n if (normalizeWhitespace) {\n lastNode.data = (lastNode.data + data).replace(reWhitespace, \" \");\n }\n else {\n lastNode.data += data;\n }\n if (this.options.withEndIndices) {\n lastNode.endIndex = this.parser.endIndex;\n }\n }\n else {\n if (normalizeWhitespace) {\n data = data.replace(reWhitespace, \" \");\n }\n var node = new node_1.Text(data);\n this.addNode(node);\n this.lastNode = node;\n }\n };\n DomHandler.prototype.oncomment = function (data) {\n if (this.lastNode && this.lastNode.type === domelementtype_1.ElementType.Comment) {\n this.lastNode.data += data;\n return;\n }\n var node = new node_1.Comment(data);\n this.addNode(node);\n this.lastNode = node;\n };\n DomHandler.prototype.oncommentend = function () {\n this.lastNode = null;\n };\n DomHandler.prototype.oncdatastart = function () {\n var text = new node_1.Text(\"\");\n var node = new node_1.NodeWithChildren(domelementtype_1.ElementType.CDATA, [text]);\n this.addNode(node);\n text.parent = node;\n this.lastNode = text;\n };\n DomHandler.prototype.oncdataend = function () {\n this.lastNode = null;\n };\n DomHandler.prototype.onprocessinginstruction = function (name, data) {\n var node = new node_1.ProcessingInstruction(name, data);\n this.addNode(node);\n };\n DomHandler.prototype.handleCallback = function (error) {\n if (typeof this.callback === \"function\") {\n this.callback(error, this.dom);\n }\n else if (error) {\n throw error;\n }\n };\n DomHandler.prototype.addNode = function (node) {\n var parent = this.tagStack[this.tagStack.length - 1];\n var previousSibling = parent.children[parent.children.length - 1];\n if (this.options.withStartIndices) {\n node.startIndex = this.parser.startIndex;\n }\n if (this.options.withEndIndices) {\n node.endIndex = this.parser.endIndex;\n }\n parent.children.push(node);\n if (previousSibling) {\n node.prev = previousSibling;\n previousSibling.next = node;\n }\n node.parent = parent;\n this.lastNode = null;\n };\n return DomHandler;\n}());\nexports.DomHandler = DomHandler;\nexports[\"default\"] = DomHandler;\n\n\n//# sourceURL=webpack://dcp/./node_modules/@selderee/plugin-htmlparser2/node_modules/domhandler/lib/index.js?");
|
|
34
|
+
|
|
35
|
+
/***/ }),
|
|
36
|
+
|
|
37
|
+
/***/ "./node_modules/@selderee/plugin-htmlparser2/node_modules/domhandler/lib/node.js":
|
|
38
|
+
/*!***************************************************************************************!*\
|
|
39
|
+
!*** ./node_modules/@selderee/plugin-htmlparser2/node_modules/domhandler/lib/node.js ***!
|
|
40
|
+
\***************************************************************************************/
|
|
41
|
+
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
42
|
+
|
|
43
|
+
"use strict";
|
|
44
|
+
eval("\nvar __extends = (this && this.__extends) || (function () {\n var extendStatics = function (d, b) {\n extendStatics = Object.setPrototypeOf ||\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\n return extendStatics(d, b);\n };\n return function (d, b) {\n if (typeof b !== \"function\" && b !== null)\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\n extendStatics(d, b);\n function __() { this.constructor = d; }\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\n };\n})();\nvar __assign = (this && this.__assign) || function () {\n __assign = Object.assign || function(t) {\n for (var s, i = 1, n = arguments.length; i < n; i++) {\n s = arguments[i];\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))\n t[p] = s[p];\n }\n return t;\n };\n return __assign.apply(this, arguments);\n};\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.cloneNode = exports.hasChildren = exports.isDocument = exports.isDirective = exports.isComment = exports.isText = exports.isCDATA = exports.isTag = exports.Element = exports.Document = exports.NodeWithChildren = exports.ProcessingInstruction = exports.Comment = exports.Text = exports.DataNode = exports.Node = void 0;\nvar domelementtype_1 = __webpack_require__(/*! domelementtype */ \"./node_modules/@selderee/plugin-htmlparser2/node_modules/domelementtype/lib/index.js\");\nvar nodeTypes = new Map([\n [domelementtype_1.ElementType.Tag, 1],\n [domelementtype_1.ElementType.Script, 1],\n [domelementtype_1.ElementType.Style, 1],\n [domelementtype_1.ElementType.Directive, 1],\n [domelementtype_1.ElementType.Text, 3],\n [domelementtype_1.ElementType.CDATA, 4],\n [domelementtype_1.ElementType.Comment, 8],\n [domelementtype_1.ElementType.Root, 9],\n]);\n/**\n * This object will be used as the prototype for Nodes when creating a\n * DOM-Level-1-compliant structure.\n */\nvar Node = /** @class */ (function () {\n /**\n *\n * @param type The type of the node.\n */\n function Node(type) {\n this.type = type;\n /** Parent of the node */\n this.parent = null;\n /** Previous sibling */\n this.prev = null;\n /** Next sibling */\n this.next = null;\n /** The start index of the node. Requires `withStartIndices` on the handler to be `true. */\n this.startIndex = null;\n /** The end index of the node. Requires `withEndIndices` on the handler to be `true. */\n this.endIndex = null;\n }\n Object.defineProperty(Node.prototype, \"nodeType\", {\n // Read-only aliases\n /**\n * [DOM spec](https://dom.spec.whatwg.org/#dom-node-nodetype)-compatible\n * node {@link type}.\n */\n get: function () {\n var _a;\n return (_a = nodeTypes.get(this.type)) !== null && _a !== void 0 ? _a : 1;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(Node.prototype, \"parentNode\", {\n // Read-write aliases for properties\n /**\n * Same as {@link parent}.\n * [DOM spec](https://dom.spec.whatwg.org)-compatible alias.\n */\n get: function () {\n return this.parent;\n },\n set: function (parent) {\n this.parent = parent;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(Node.prototype, \"previousSibling\", {\n /**\n * Same as {@link prev}.\n * [DOM spec](https://dom.spec.whatwg.org)-compatible alias.\n */\n get: function () {\n return this.prev;\n },\n set: function (prev) {\n this.prev = prev;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(Node.prototype, \"nextSibling\", {\n /**\n * Same as {@link next}.\n * [DOM spec](https://dom.spec.whatwg.org)-compatible alias.\n */\n get: function () {\n return this.next;\n },\n set: function (next) {\n this.next = next;\n },\n enumerable: false,\n configurable: true\n });\n /**\n * Clone this node, and optionally its children.\n *\n * @param recursive Clone child nodes as well.\n * @returns A clone of the node.\n */\n Node.prototype.cloneNode = function (recursive) {\n if (recursive === void 0) { recursive = false; }\n return cloneNode(this, recursive);\n };\n return Node;\n}());\nexports.Node = Node;\n/**\n * A node that contains some data.\n */\nvar DataNode = /** @class */ (function (_super) {\n __extends(DataNode, _super);\n /**\n * @param type The type of the node\n * @param data The content of the data node\n */\n function DataNode(type, data) {\n var _this = _super.call(this, type) || this;\n _this.data = data;\n return _this;\n }\n Object.defineProperty(DataNode.prototype, \"nodeValue\", {\n /**\n * Same as {@link data}.\n * [DOM spec](https://dom.spec.whatwg.org)-compatible alias.\n */\n get: function () {\n return this.data;\n },\n set: function (data) {\n this.data = data;\n },\n enumerable: false,\n configurable: true\n });\n return DataNode;\n}(Node));\nexports.DataNode = DataNode;\n/**\n * Text within the document.\n */\nvar Text = /** @class */ (function (_super) {\n __extends(Text, _super);\n function Text(data) {\n return _super.call(this, domelementtype_1.ElementType.Text, data) || this;\n }\n return Text;\n}(DataNode));\nexports.Text = Text;\n/**\n * Comments within the document.\n */\nvar Comment = /** @class */ (function (_super) {\n __extends(Comment, _super);\n function Comment(data) {\n return _super.call(this, domelementtype_1.ElementType.Comment, data) || this;\n }\n return Comment;\n}(DataNode));\nexports.Comment = Comment;\n/**\n * Processing instructions, including doc types.\n */\nvar ProcessingInstruction = /** @class */ (function (_super) {\n __extends(ProcessingInstruction, _super);\n function ProcessingInstruction(name, data) {\n var _this = _super.call(this, domelementtype_1.ElementType.Directive, data) || this;\n _this.name = name;\n return _this;\n }\n return ProcessingInstruction;\n}(DataNode));\nexports.ProcessingInstruction = ProcessingInstruction;\n/**\n * A `Node` that can have children.\n */\nvar NodeWithChildren = /** @class */ (function (_super) {\n __extends(NodeWithChildren, _super);\n /**\n * @param type Type of the node.\n * @param children Children of the node. Only certain node types can have children.\n */\n function NodeWithChildren(type, children) {\n var _this = _super.call(this, type) || this;\n _this.children = children;\n return _this;\n }\n Object.defineProperty(NodeWithChildren.prototype, \"firstChild\", {\n // Aliases\n /** First child of the node. */\n get: function () {\n var _a;\n return (_a = this.children[0]) !== null && _a !== void 0 ? _a : null;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(NodeWithChildren.prototype, \"lastChild\", {\n /** Last child of the node. */\n get: function () {\n return this.children.length > 0\n ? this.children[this.children.length - 1]\n : null;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(NodeWithChildren.prototype, \"childNodes\", {\n /**\n * Same as {@link children}.\n * [DOM spec](https://dom.spec.whatwg.org)-compatible alias.\n */\n get: function () {\n return this.children;\n },\n set: function (children) {\n this.children = children;\n },\n enumerable: false,\n configurable: true\n });\n return NodeWithChildren;\n}(Node));\nexports.NodeWithChildren = NodeWithChildren;\n/**\n * The root node of the document.\n */\nvar Document = /** @class */ (function (_super) {\n __extends(Document, _super);\n function Document(children) {\n return _super.call(this, domelementtype_1.ElementType.Root, children) || this;\n }\n return Document;\n}(NodeWithChildren));\nexports.Document = Document;\n/**\n * An element within the DOM.\n */\nvar Element = /** @class */ (function (_super) {\n __extends(Element, _super);\n /**\n * @param name Name of the tag, eg. `div`, `span`.\n * @param attribs Object mapping attribute names to attribute values.\n * @param children Children of the node.\n */\n function Element(name, attribs, children, type) {\n if (children === void 0) { children = []; }\n if (type === void 0) { type = name === \"script\"\n ? domelementtype_1.ElementType.Script\n : name === \"style\"\n ? domelementtype_1.ElementType.Style\n : domelementtype_1.ElementType.Tag; }\n var _this = _super.call(this, type, children) || this;\n _this.name = name;\n _this.attribs = attribs;\n return _this;\n }\n Object.defineProperty(Element.prototype, \"tagName\", {\n // DOM Level 1 aliases\n /**\n * Same as {@link name}.\n * [DOM spec](https://dom.spec.whatwg.org)-compatible alias.\n */\n get: function () {\n return this.name;\n },\n set: function (name) {\n this.name = name;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(Element.prototype, \"attributes\", {\n get: function () {\n var _this = this;\n return Object.keys(this.attribs).map(function (name) {\n var _a, _b;\n return ({\n name: name,\n value: _this.attribs[name],\n namespace: (_a = _this[\"x-attribsNamespace\"]) === null || _a === void 0 ? void 0 : _a[name],\n prefix: (_b = _this[\"x-attribsPrefix\"]) === null || _b === void 0 ? void 0 : _b[name],\n });\n });\n },\n enumerable: false,\n configurable: true\n });\n return Element;\n}(NodeWithChildren));\nexports.Element = Element;\n/**\n * @param node Node to check.\n * @returns `true` if the node is a `Element`, `false` otherwise.\n */\nfunction isTag(node) {\n return (0, domelementtype_1.isTag)(node);\n}\nexports.isTag = isTag;\n/**\n * @param node Node to check.\n * @returns `true` if the node has the type `CDATA`, `false` otherwise.\n */\nfunction isCDATA(node) {\n return node.type === domelementtype_1.ElementType.CDATA;\n}\nexports.isCDATA = isCDATA;\n/**\n * @param node Node to check.\n * @returns `true` if the node has the type `Text`, `false` otherwise.\n */\nfunction isText(node) {\n return node.type === domelementtype_1.ElementType.Text;\n}\nexports.isText = isText;\n/**\n * @param node Node to check.\n * @returns `true` if the node has the type `Comment`, `false` otherwise.\n */\nfunction isComment(node) {\n return node.type === domelementtype_1.ElementType.Comment;\n}\nexports.isComment = isComment;\n/**\n * @param node Node to check.\n * @returns `true` if the node has the type `ProcessingInstruction`, `false` otherwise.\n */\nfunction isDirective(node) {\n return node.type === domelementtype_1.ElementType.Directive;\n}\nexports.isDirective = isDirective;\n/**\n * @param node Node to check.\n * @returns `true` if the node has the type `ProcessingInstruction`, `false` otherwise.\n */\nfunction isDocument(node) {\n return node.type === domelementtype_1.ElementType.Root;\n}\nexports.isDocument = isDocument;\n/**\n * @param node Node to check.\n * @returns `true` if the node is a `NodeWithChildren` (has children), `false` otherwise.\n */\nfunction hasChildren(node) {\n return Object.prototype.hasOwnProperty.call(node, \"children\");\n}\nexports.hasChildren = hasChildren;\n/**\n * Clone a node, and optionally its children.\n *\n * @param recursive Clone child nodes as well.\n * @returns A clone of the node.\n */\nfunction cloneNode(node, recursive) {\n if (recursive === void 0) { recursive = false; }\n var result;\n if (isText(node)) {\n result = new Text(node.data);\n }\n else if (isComment(node)) {\n result = new Comment(node.data);\n }\n else if (isTag(node)) {\n var children = recursive ? cloneChildren(node.children) : [];\n var clone_1 = new Element(node.name, __assign({}, node.attribs), children);\n children.forEach(function (child) { return (child.parent = clone_1); });\n if (node.namespace != null) {\n clone_1.namespace = node.namespace;\n }\n if (node[\"x-attribsNamespace\"]) {\n clone_1[\"x-attribsNamespace\"] = __assign({}, node[\"x-attribsNamespace\"]);\n }\n if (node[\"x-attribsPrefix\"]) {\n clone_1[\"x-attribsPrefix\"] = __assign({}, node[\"x-attribsPrefix\"]);\n }\n result = clone_1;\n }\n else if (isCDATA(node)) {\n var children = recursive ? cloneChildren(node.children) : [];\n var clone_2 = new NodeWithChildren(domelementtype_1.ElementType.CDATA, children);\n children.forEach(function (child) { return (child.parent = clone_2); });\n result = clone_2;\n }\n else if (isDocument(node)) {\n var children = recursive ? cloneChildren(node.children) : [];\n var clone_3 = new Document(children);\n children.forEach(function (child) { return (child.parent = clone_3); });\n if (node[\"x-mode\"]) {\n clone_3[\"x-mode\"] = node[\"x-mode\"];\n }\n result = clone_3;\n }\n else if (isDirective(node)) {\n var instruction = new ProcessingInstruction(node.name, node.data);\n if (node[\"x-name\"] != null) {\n instruction[\"x-name\"] = node[\"x-name\"];\n instruction[\"x-publicId\"] = node[\"x-publicId\"];\n instruction[\"x-systemId\"] = node[\"x-systemId\"];\n }\n result = instruction;\n }\n else {\n throw new Error(\"Not implemented yet: \".concat(node.type));\n }\n result.startIndex = node.startIndex;\n result.endIndex = node.endIndex;\n if (node.sourceCodeLocation != null) {\n result.sourceCodeLocation = node.sourceCodeLocation;\n }\n return result;\n}\nexports.cloneNode = cloneNode;\nfunction cloneChildren(childs) {\n var children = childs.map(function (child) { return cloneNode(child, true); });\n for (var i = 1; i < children.length; i++) {\n children[i].prev = children[i - 1];\n children[i - 1].next = children[i];\n }\n return children;\n}\n\n\n//# sourceURL=webpack://dcp/./node_modules/@selderee/plugin-htmlparser2/node_modules/domhandler/lib/node.js?");
|
|
45
|
+
|
|
46
|
+
/***/ }),
|
|
47
|
+
|
|
15
48
|
/***/ "./node_modules/aes-js/index.js":
|
|
16
49
|
/*!**************************************!*\
|
|
17
50
|
!*** ./node_modules/aes-js/index.js ***!
|
|
@@ -2541,7 +2574,7 @@ eval("module.exports = __webpack_require__(/*! ./lib/html-to-text */ \"./node_mo
|
|
|
2541
2574
|
\*************************************************************/
|
|
2542
2575
|
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
2543
2576
|
|
|
2544
|
-
eval("\nconst { trimCharacter } = __webpack_require__(/*! ./helper */ \"./node_modules/html-to-text/lib/helper.js\");\n// eslint-disable-next-line no-unused-vars\nconst { StackItem, BlockStackItem, TableCellStackItem, TableRowStackItem, TableStackItem, TransformerStackItem }\n = __webpack_require__(/*! ./stack-item */ \"./node_modules/html-to-text/lib/stack-item.js\");\nconst { tableToString } = __webpack_require__(/*! ./table-printer */ \"./node_modules/html-to-text/lib/table-printer.js\");\nconst { WhitespaceProcessor } = __webpack_require__(/*! ./whitespace-processor */ \"./node_modules/html-to-text/lib/whitespace-processor.js\");\n\n// eslint-disable-next-line import/no-unassigned-import\n__webpack_require__(/*! ./typedefs */ \"./node_modules/html-to-text/lib/typedefs.js\");\n\n\n/**\n * Helps to build text from inline and block elements.\n *\n * @class BlockTextBuilder\n */\nclass BlockTextBuilder {\n\n /**\n * Creates an instance of BlockTextBuilder.\n *\n * @param { Options } options HtmlToText options.\n */\n constructor (options) {\n this.options = options;\n this.whitepaceProcessor = new WhitespaceProcessor(options);\n /** @type { StackItem } */\n this._stackItem = new BlockStackItem(options);\n /** @type { TransformerStackItem } */\n this._wordTransformer = undefined;\n }\n\n /**\n * Put a word-by-word transform function onto the transformations stack.\n *\n * Mainly used for uppercasing. Can be bypassed to add unformatted text such as URLs.\n *\n * Word transformations applied before wrapping.\n *\n * @param { (str: string) => string } wordTransform Word transformation function.\n */\n pushWordTransform (wordTransform) {\n this._wordTransformer = new TransformerStackItem(this._wordTransformer, wordTransform);\n }\n\n /**\n * Remove a function from the word transformations stack.\n *\n * @returns { (str: string) => string } A function that was removed.\n */\n popWordTransform () {\n if (!this._wordTransformer) { return undefined; }\n const transform = this._wordTransformer.transform;\n this._wordTransformer = this._wordTransformer.next;\n return transform;\n }\n\n /** @returns { (str: string) => string } */\n _getCombinedWordTransformer () {\n const applyTransformer = (str, transformer) =>\n ((transformer) ? applyTransformer(transformer.transform(str), transformer.next) : str);\n return (str) => applyTransformer(str, this._wordTransformer);\n }\n\n _popStackItem () {\n const item = this._stackItem;\n this._stackItem = item.next;\n return item;\n }\n\n /**\n * Add a line break into currently built block.\n */\n addLineBreak () {\n if (!(\n this._stackItem instanceof BlockStackItem\n || this._stackItem instanceof TableCellStackItem\n )) { return; }\n if (this._stackItem.isPre) {\n this._stackItem.rawText += '\\n';\n } else {\n this._stackItem.inlineTextBuilder.startNewLine();\n }\n }\n\n /**\n * Allow to break line in case directly following text will not fit.\n */\n addWordBreakOpportunity () {\n if (\n this._stackItem instanceof BlockStackItem\n || this._stackItem instanceof TableCellStackItem\n ) {\n this._stackItem.inlineTextBuilder.wordBreakOpportunity = true;\n }\n }\n\n /**\n * Add a node inline into the currently built block.\n *\n * @param { string } str\n * Text content of a node to add.\n *\n * @param { object | boolean } [ optionsObjectOrNoWordTransform ]\n * Object holding the parameters of the operation.\n *\n * Boolean value is deprecated.\n *\n * @param { boolean } [ optionsObjectOrNoWordTransform.noWordTransform = false ]\n * Ignore word transformers if there are any.\n */\n addInline (str, optionsObjectOrNoWordTransform = {}) {\n if (typeof optionsObjectOrNoWordTransform === 'object') {\n this._addInline(str, optionsObjectOrNoWordTransform);\n } else {\n this._addInline(str, { noWordTransform: optionsObjectOrNoWordTransform });\n }\n }\n\n _addInline (str, { noWordTransform = false } = {}) {\n if (!(\n this._stackItem instanceof BlockStackItem\n || this._stackItem instanceof TableCellStackItem\n )) { return; }\n\n if (this._stackItem.isPre) {\n this._stackItem.rawText += str;\n return;\n }\n\n if (\n this.whitepaceProcessor.testContainsWords(str) || // There are words to add;\n (str.length && !this._stackItem.stashedLineBreaks) // or at least spaces to take into account.\n ) {\n if (this._stackItem.stashedLineBreaks) {\n this._stackItem.inlineTextBuilder.startNewLine(this._stackItem.stashedLineBreaks);\n }\n this.whitepaceProcessor.shrinkWrapAdd(\n str,\n this._stackItem.inlineTextBuilder,\n (this._wordTransformer && !noWordTransform) ? this._getCombinedWordTransformer() : undefined\n );\n this._stackItem.stashedLineBreaks = 0; // inline text doesn't introduce line breaks\n }\n }\n\n /**\n * Start building a new block.\n *\n * @param { object | number } [optionsObjectOrLeadingLineBreaks]\n * Object holding the parameters of the block.\n *\n * Number value is deprecated.\n *\n * @param { number } [optionsObjectOrLeadingLineBreaks.leadingLineBreaks = 1]\n * This block should have at least this number of line breaks to separate if from any preceding block.\n *\n * @param { number } [optionsObjectOrLeadingLineBreaks.reservedLineLength = 0]\n * Reserve this number of characters on each line for block markup.\n *\n * @param { boolean } [optionsObjectOrLeadingLineBreaks.isPre = false]\n * Should HTML whitespace be preserved inside this block.\n *\n * @param { number } [reservedLineLength]\n * Deprecated.\n *\n * @param { boolean } [isPre]\n * Deprecated.\n */\n openBlock (optionsObjectOrLeadingLineBreaks = {}, reservedLineLength = undefined, isPre = undefined) {\n if (typeof optionsObjectOrLeadingLineBreaks === 'object') {\n this._openBlock(optionsObjectOrLeadingLineBreaks);\n } else {\n this._openBlock({\n isPre: isPre,\n leadingLineBreaks: optionsObjectOrLeadingLineBreaks,\n reservedLineLength: reservedLineLength,\n });\n }\n }\n\n _openBlock ({ leadingLineBreaks = 1, reservedLineLength = 0, isPre = false } = {}) {\n const maxLineLength = Math.max(20, this._stackItem.inlineTextBuilder.maxLineLength - reservedLineLength);\n this._stackItem = new BlockStackItem(\n this.options,\n this._stackItem,\n leadingLineBreaks,\n maxLineLength\n );\n if (isPre) { this._stackItem.isPre = true; }\n }\n\n /**\n * Finalize currently built block, add it's content to the parent block.\n *\n * @param { object | number } [optionsObjectOrTrailingLineBreaks]\n * Object holding the parameters of the block.\n *\n * Number value is deprecated.\n *\n * @param { number } [optionsObjectOrTrailingLineBreaks.trailingLineBreaks = 1]\n * This block should have at least this number of line breaks to separate it from any following block.\n *\n * @param { (str: string) => string } [optionsObjectOrTrailingLineBreaks.blockTransform = undefined]\n * A function to transform the block text before adding to the parent block.\n * This happens after word wrap and should be used in combination with reserved line length\n * in order to keep line lengths correct.\n * Used for whole block markup.\n *\n * @param { (str: string) => string } [blockTransform]\n * Deprecated.\n */\n closeBlock (optionsObjectOrTrailingLineBreaks = {}, blockTransform = undefined) {\n if (typeof optionsObjectOrTrailingLineBreaks === 'object') {\n this._closeBlock(optionsObjectOrTrailingLineBreaks);\n } else {\n this._closeBlock({\n trailingLineBreaks: optionsObjectOrTrailingLineBreaks,\n blockTransform: blockTransform,\n });\n }\n }\n\n _closeBlock ({ trailingLineBreaks = 1, blockTransform = undefined } = {}) {\n const block = this._popStackItem();\n const blockText = (blockTransform) ? blockTransform(getText(block)) : getText(block);\n addText(this._stackItem, blockText, block.leadingLineBreaks, Math.max(block.stashedLineBreaks, trailingLineBreaks));\n }\n\n /**\n * Start building a table.\n */\n openTable () {\n this._stackItem = new TableStackItem(this._stackItem);\n }\n\n /**\n * Start building a table row.\n */\n openTableRow () {\n if (!(this._stackItem instanceof TableStackItem)) {\n throw new Error('Can\\'t add table row to something that is not a table! Check the formatter.');\n }\n this._stackItem = new TableRowStackItem(this._stackItem);\n }\n\n /**\n * Start building a table cell.\n *\n * @param { object | number } [optionsObjectOrMaxColumnWidth = undefined]\n * Object holding the parameters of the cell.\n *\n * Number value is deprecated.\n *\n * @param { number } [optionsObjectOrMaxColumnWidth.maxColumnWidth = undefined]\n * Wrap cell content to this width. Fall back to global wordwrap value if undefined.\n */\n openTableCell (optionsObjectOrMaxColumnWidth = {}) {\n if (typeof optionsObjectOrMaxColumnWidth === 'object') {\n this._openTableCell(optionsObjectOrMaxColumnWidth);\n } else {\n this._openTableCell({ maxColumnWidth: optionsObjectOrMaxColumnWidth });\n }\n }\n\n _openTableCell ({ maxColumnWidth = undefined } = {}) {\n if (!(this._stackItem instanceof TableRowStackItem)) {\n throw new Error('Can\\'t add table cell to something that is not a table row! Check the formatter.');\n }\n this._stackItem = new TableCellStackItem(this.options, this._stackItem, maxColumnWidth);\n }\n\n /**\n * Finalize currently built table cell and add it to parent table row's cells.\n *\n * @param { object | number } [optionsObjectOrColspan]\n * Object holding the parameters of the cell.\n *\n * Number value is deprecated.\n *\n * @param { number } [optionsObjectOrColspan.colspan = 1] How many columns this cell should occupy.\n * @param { number } [optionsObjectOrColspan.rowspan = 1] How many rows this cell should occupy.\n *\n * @param { number } [rowspan] Deprecated.\n */\n closeTableCell (optionsObjectOrColspan = {}, rowspan = undefined) {\n if (typeof optionsObjectOrColspan === 'object') {\n this._closeTableCell(optionsObjectOrColspan);\n } else {\n this._closeTableCell({\n colspan: optionsObjectOrColspan,\n rowspan: rowspan,\n });\n }\n }\n\n _closeTableCell ({ colspan = 1, rowspan = 1 } = {}) {\n const cell = this._popStackItem();\n const text = trimCharacter(getText(cell), '\\n');\n cell.next.cells.push({ colspan: colspan, rowspan: rowspan, text: text });\n }\n\n /**\n * Finalize currently built table row and add it to parent table's rows.\n */\n closeTableRow () {\n const row = this._popStackItem();\n row.next.rows.push(row.cells);\n }\n\n /**\n * Finalize currently built table and add the rendered text to the parent block.\n *\n * @param { object | number } [optionsObjectOrColSpacing]\n * Object holding the parameters of the table.\n *\n * Number value is depreceted.\n *\n * @param { number } [optionsObjectOrColSpacing.colSpacing = 3]\n * Number of spaces between table columns.\n *\n * @param { number } [optionsObjectOrColSpacing.rowSpacing = 0]\n * Number of empty lines between table rows.\n *\n * @param { number } [optionsObjectOrColSpacing.leadingLineBreaks = 2]\n * This table should have at least this number of line breaks to separate if from any preceding block.\n *\n * @param { number } [optionsObjectOrColSpacing.trailingLineBreaks = 2]\n * This table should have at least this number of line breaks to separate it from any following block.\n *\n * @param { number } [rowSpacing]\n * Deprecated.\n *\n * @param { number } [leadingLineBreaks]\n * Deprecated.\n *\n * @param { number } [trailingLineBreaks]\n * Deprecated.\n */\n closeTable (\n optionsObjectOrColSpacing = {},\n rowSpacing = undefined,\n leadingLineBreaks = undefined,\n trailingLineBreaks = undefined\n ) {\n if (typeof optionsObjectOrColSpacing === 'object') {\n this._closeTable(optionsObjectOrColSpacing);\n } else {\n this._closeTable({\n colSpacing: optionsObjectOrColSpacing,\n leadingLineBreaks: leadingLineBreaks,\n rowSpacing: rowSpacing,\n trailingLineBreaks: trailingLineBreaks\n });\n }\n }\n\n _closeTable ({ colSpacing = 3, rowSpacing = 0, leadingLineBreaks = 2, trailingLineBreaks = 2 } = {}) {\n const table = this._popStackItem();\n const output = tableToString(table.rows, rowSpacing, colSpacing);\n if (output) {\n addText(this._stackItem, output, leadingLineBreaks, trailingLineBreaks);\n }\n }\n\n /**\n * Return the rendered text content of this builder.\n *\n * @returns { string }\n */\n toString () {\n return getText(this._stackItem.getRoot());\n // There should only be the root item if everything is closed properly.\n }\n\n}\n\nfunction getText (stackItem) {\n if (!(\n stackItem instanceof BlockStackItem\n || stackItem instanceof TableCellStackItem\n )) {\n throw new Error('Only blocks and table cells can be requested for text contents.');\n }\n return (stackItem.inlineTextBuilder.isEmpty())\n ? stackItem.rawText\n : stackItem.rawText + stackItem.inlineTextBuilder.toString();\n}\n\nfunction addText (stackItem, text, leadingLineBreaks, trailingLineBreaks) {\n if (!(\n stackItem instanceof BlockStackItem\n || stackItem instanceof TableCellStackItem\n )) {\n throw new Error('Only blocks and table cells can contain text.');\n }\n const parentText = getText(stackItem);\n const lineBreaks = Math.max(stackItem.stashedLineBreaks, leadingLineBreaks);\n stackItem.inlineTextBuilder.clear();\n if (parentText) {\n stackItem.rawText = parentText + '\\n'.repeat(lineBreaks) + text;\n } else {\n stackItem.rawText = text;\n stackItem.leadingLineBreaks = lineBreaks;\n }\n stackItem.stashedLineBreaks = trailingLineBreaks;\n}\n\nmodule.exports = { BlockTextBuilder: BlockTextBuilder };\n\n\n//# sourceURL=webpack://dcp/./node_modules/html-to-text/lib/block-text-builder.js?");
|
|
2577
|
+
eval("\n// eslint-disable-next-line no-unused-vars\nconst { Picker } = __webpack_require__(/*! selderee */ \"./node_modules/selderee/lib/selderee.cjs\");\n\nconst { trimCharacter } = __webpack_require__(/*! ./helper */ \"./node_modules/html-to-text/lib/helper.js\");\n// eslint-disable-next-line no-unused-vars\nconst { StackItem, BlockStackItem, TableCellStackItem, TableRowStackItem, TableStackItem, TransformerStackItem }\n = __webpack_require__(/*! ./stack-item */ \"./node_modules/html-to-text/lib/stack-item.js\");\nconst { tableToString } = __webpack_require__(/*! ./table-printer */ \"./node_modules/html-to-text/lib/table-printer.js\");\nconst { WhitespaceProcessor } = __webpack_require__(/*! ./whitespace-processor */ \"./node_modules/html-to-text/lib/whitespace-processor.js\");\n\n// eslint-disable-next-line import/no-unassigned-import\n__webpack_require__(/*! ./typedefs */ \"./node_modules/html-to-text/lib/typedefs.js\");\n\n\n/**\n * Helps to build text from inline and block elements.\n *\n * @class BlockTextBuilder\n */\nclass BlockTextBuilder {\n\n /**\n * Creates an instance of BlockTextBuilder.\n *\n * @param { Options } options HtmlToText options.\n * @param { Picker<DomNode, TagDefinition> } picker Selectors decision tree picker.\n */\n constructor (options, picker) {\n this.options = options;\n this.picker = picker;\n this.whitespaceProcessor = new WhitespaceProcessor(options);\n /** @type { StackItem } */\n this._stackItem = new BlockStackItem(options);\n /** @type { TransformerStackItem } */\n this._wordTransformer = undefined;\n }\n\n /**\n * Put a word-by-word transform function onto the transformations stack.\n *\n * Mainly used for uppercasing. Can be bypassed to add unformatted text such as URLs.\n *\n * Word transformations applied before wrapping.\n *\n * @param { (str: string) => string } wordTransform Word transformation function.\n */\n pushWordTransform (wordTransform) {\n this._wordTransformer = new TransformerStackItem(this._wordTransformer, wordTransform);\n }\n\n /**\n * Remove a function from the word transformations stack.\n *\n * @returns { (str: string) => string } A function that was removed.\n */\n popWordTransform () {\n if (!this._wordTransformer) { return undefined; }\n const transform = this._wordTransformer.transform;\n this._wordTransformer = this._wordTransformer.next;\n return transform;\n }\n\n /** @returns { (str: string) => string } */\n _getCombinedWordTransformer () {\n const applyTransformer = (str, transformer) =>\n ((transformer) ? applyTransformer(transformer.transform(str), transformer.next) : str);\n return (str) => applyTransformer(str, this._wordTransformer);\n }\n\n _popStackItem () {\n const item = this._stackItem;\n this._stackItem = item.next;\n return item;\n }\n\n /**\n * Add a line break into currently built block.\n */\n addLineBreak () {\n if (!(\n this._stackItem instanceof BlockStackItem\n || this._stackItem instanceof TableCellStackItem\n )) { return; }\n if (this._stackItem.isPre) {\n this._stackItem.rawText += '\\n';\n } else {\n this._stackItem.inlineTextBuilder.startNewLine();\n }\n }\n\n /**\n * Allow to break line in case directly following text will not fit.\n */\n addWordBreakOpportunity () {\n if (\n this._stackItem instanceof BlockStackItem\n || this._stackItem instanceof TableCellStackItem\n ) {\n this._stackItem.inlineTextBuilder.wordBreakOpportunity = true;\n }\n }\n\n /**\n * Add a node inline into the currently built block.\n *\n * @param { string } str\n * Text content of a node to add.\n *\n * @param { object | boolean } [ optionsObjectOrNoWordTransform ]\n * Object holding the parameters of the operation.\n *\n * Boolean value is deprecated.\n *\n * @param { boolean } [ optionsObjectOrNoWordTransform.noWordTransform = false ]\n * Ignore word transformers if there are any.\n */\n addInline (str, optionsObjectOrNoWordTransform = {}) {\n if (typeof optionsObjectOrNoWordTransform === 'object') {\n this._addInline(str, optionsObjectOrNoWordTransform);\n } else {\n this._addInline(str, { noWordTransform: optionsObjectOrNoWordTransform });\n }\n }\n\n _addInline (str, { noWordTransform = false } = {}) {\n if (!(\n this._stackItem instanceof BlockStackItem\n || this._stackItem instanceof TableCellStackItem\n )) { return; }\n\n if (this._stackItem.isPre) {\n this._stackItem.rawText += str;\n return;\n }\n\n if (\n str.length === 0 || // empty string\n (\n this._stackItem.stashedLineBreaks && // stashed linebreaks make whitespace irrelevant\n !this.whitespaceProcessor.testContainsWords(str) // no words to add\n )\n ) { return; }\n\n if (this.options.preserveNewlines) {\n const newlinesNumber = this.whitespaceProcessor.countNewlinesNoWords(str);\n if (newlinesNumber > 0) {\n this._stackItem.inlineTextBuilder.startNewLine(newlinesNumber);\n // keep stashedLineBreaks unchanged\n return;\n }\n }\n\n if (this._stackItem.stashedLineBreaks) {\n this._stackItem.inlineTextBuilder.startNewLine(this._stackItem.stashedLineBreaks);\n }\n this.whitespaceProcessor.shrinkWrapAdd(\n str,\n this._stackItem.inlineTextBuilder,\n (this._wordTransformer && !noWordTransform) ? this._getCombinedWordTransformer() : undefined\n );\n this._stackItem.stashedLineBreaks = 0; // inline text doesn't introduce line breaks\n }\n\n /**\n * Start building a new block.\n *\n * @param { object | number } [optionsObjectOrLeadingLineBreaks]\n * Object holding the parameters of the block.\n *\n * Number value is deprecated.\n *\n * @param { number } [optionsObjectOrLeadingLineBreaks.leadingLineBreaks = 1]\n * This block should have at least this number of line breaks to separate if from any preceding block.\n *\n * @param { number } [optionsObjectOrLeadingLineBreaks.reservedLineLength = 0]\n * Reserve this number of characters on each line for block markup.\n *\n * @param { boolean } [optionsObjectOrLeadingLineBreaks.isPre = false]\n * Should HTML whitespace be preserved inside this block.\n *\n * @param { number } [reservedLineLength]\n * Deprecated.\n *\n * @param { boolean } [isPre]\n * Deprecated.\n */\n openBlock (optionsObjectOrLeadingLineBreaks = {}, reservedLineLength = undefined, isPre = undefined) {\n if (typeof optionsObjectOrLeadingLineBreaks === 'object') {\n this._openBlock(optionsObjectOrLeadingLineBreaks);\n } else {\n this._openBlock({\n isPre: isPre,\n leadingLineBreaks: optionsObjectOrLeadingLineBreaks,\n reservedLineLength: reservedLineLength,\n });\n }\n }\n\n _openBlock ({ leadingLineBreaks = 1, reservedLineLength = 0, isPre = false } = {}) {\n const maxLineLength = Math.max(20, this._stackItem.inlineTextBuilder.maxLineLength - reservedLineLength);\n this._stackItem = new BlockStackItem(\n this.options,\n this._stackItem,\n leadingLineBreaks,\n maxLineLength\n );\n if (isPre) { this._stackItem.isPre = true; }\n }\n\n /**\n * Finalize currently built block, add it's content to the parent block.\n *\n * @param { object | number } [optionsObjectOrTrailingLineBreaks]\n * Object holding the parameters of the block.\n *\n * Number value is deprecated.\n *\n * @param { number } [optionsObjectOrTrailingLineBreaks.trailingLineBreaks = 1]\n * This block should have at least this number of line breaks to separate it from any following block.\n *\n * @param { (str: string) => string } [optionsObjectOrTrailingLineBreaks.blockTransform = undefined]\n * A function to transform the block text before adding to the parent block.\n * This happens after word wrap and should be used in combination with reserved line length\n * in order to keep line lengths correct.\n * Used for whole block markup.\n *\n * @param { (str: string) => string } [blockTransform]\n * Deprecated.\n */\n closeBlock (optionsObjectOrTrailingLineBreaks = {}, blockTransform = undefined) {\n if (typeof optionsObjectOrTrailingLineBreaks === 'object') {\n this._closeBlock(optionsObjectOrTrailingLineBreaks);\n } else {\n this._closeBlock({\n trailingLineBreaks: optionsObjectOrTrailingLineBreaks,\n blockTransform: blockTransform,\n });\n }\n }\n\n _closeBlock ({ trailingLineBreaks = 1, blockTransform = undefined } = {}) {\n const block = this._popStackItem();\n const blockText = (blockTransform) ? blockTransform(getText(block)) : getText(block);\n addText(this._stackItem, blockText, block.leadingLineBreaks, Math.max(block.stashedLineBreaks, trailingLineBreaks));\n }\n\n /**\n * Start building a table.\n */\n openTable () {\n this._stackItem = new TableStackItem(this._stackItem);\n }\n\n /**\n * Start building a table row.\n */\n openTableRow () {\n if (!(this._stackItem instanceof TableStackItem)) {\n throw new Error('Can\\'t add table row to something that is not a table! Check the formatter.');\n }\n this._stackItem = new TableRowStackItem(this._stackItem);\n }\n\n /**\n * Start building a table cell.\n *\n * @param { object | number } [optionsObjectOrMaxColumnWidth = undefined]\n * Object holding the parameters of the cell.\n *\n * Number value is deprecated.\n *\n * @param { number } [optionsObjectOrMaxColumnWidth.maxColumnWidth = undefined]\n * Wrap cell content to this width. Fall back to global wordwrap value if undefined.\n */\n openTableCell (optionsObjectOrMaxColumnWidth = {}) {\n if (typeof optionsObjectOrMaxColumnWidth === 'object') {\n this._openTableCell(optionsObjectOrMaxColumnWidth);\n } else {\n this._openTableCell({ maxColumnWidth: optionsObjectOrMaxColumnWidth });\n }\n }\n\n _openTableCell ({ maxColumnWidth = undefined } = {}) {\n if (!(this._stackItem instanceof TableRowStackItem)) {\n throw new Error('Can\\'t add table cell to something that is not a table row! Check the formatter.');\n }\n this._stackItem = new TableCellStackItem(this.options, this._stackItem, maxColumnWidth);\n }\n\n /**\n * Finalize currently built table cell and add it to parent table row's cells.\n *\n * @param { object | number } [optionsObjectOrColspan]\n * Object holding the parameters of the cell.\n *\n * Number value is deprecated.\n *\n * @param { number } [optionsObjectOrColspan.colspan = 1] How many columns this cell should occupy.\n * @param { number } [optionsObjectOrColspan.rowspan = 1] How many rows this cell should occupy.\n *\n * @param { number } [rowspan] Deprecated.\n */\n closeTableCell (optionsObjectOrColspan = {}, rowspan = undefined) {\n if (typeof optionsObjectOrColspan === 'object') {\n this._closeTableCell(optionsObjectOrColspan);\n } else {\n this._closeTableCell({\n colspan: optionsObjectOrColspan,\n rowspan: rowspan,\n });\n }\n }\n\n _closeTableCell ({ colspan = 1, rowspan = 1 } = {}) {\n const cell = this._popStackItem();\n const text = trimCharacter(getText(cell), '\\n');\n cell.next.cells.push({ colspan: colspan, rowspan: rowspan, text: text });\n }\n\n /**\n * Finalize currently built table row and add it to parent table's rows.\n */\n closeTableRow () {\n const row = this._popStackItem();\n row.next.rows.push(row.cells);\n }\n\n /**\n * Finalize currently built table and add the rendered text to the parent block.\n *\n * @param { object | number } [optionsObjectOrColSpacing]\n * Object holding the parameters of the table.\n *\n * Number value is deprecated.\n *\n * @param { number } [optionsObjectOrColSpacing.colSpacing = 3]\n * Number of spaces between table columns.\n *\n * @param { number } [optionsObjectOrColSpacing.rowSpacing = 0]\n * Number of empty lines between table rows.\n *\n * @param { number } [optionsObjectOrColSpacing.leadingLineBreaks = 2]\n * This table should have at least this number of line breaks to separate if from any preceding block.\n *\n * @param { number } [optionsObjectOrColSpacing.trailingLineBreaks = 2]\n * This table should have at least this number of line breaks to separate it from any following block.\n *\n * @param { number } [rowSpacing]\n * Deprecated.\n *\n * @param { number } [leadingLineBreaks]\n * Deprecated.\n *\n * @param { number } [trailingLineBreaks]\n * Deprecated.\n */\n closeTable (\n optionsObjectOrColSpacing = {},\n rowSpacing = undefined,\n leadingLineBreaks = undefined,\n trailingLineBreaks = undefined\n ) {\n if (typeof optionsObjectOrColSpacing === 'object') {\n this._closeTable(optionsObjectOrColSpacing);\n } else {\n this._closeTable({\n colSpacing: optionsObjectOrColSpacing,\n leadingLineBreaks: leadingLineBreaks,\n rowSpacing: rowSpacing,\n trailingLineBreaks: trailingLineBreaks\n });\n }\n }\n\n _closeTable ({ colSpacing = 3, rowSpacing = 0, leadingLineBreaks = 2, trailingLineBreaks = 2 } = {}) {\n const table = this._popStackItem();\n const output = tableToString(table.rows, rowSpacing, colSpacing);\n if (output) {\n addText(this._stackItem, output, leadingLineBreaks, trailingLineBreaks);\n }\n }\n\n /**\n * Return the rendered text content of this builder.\n *\n * @returns { string }\n */\n toString () {\n return getText(this._stackItem.getRoot());\n // There should only be the root item if everything is closed properly.\n }\n\n}\n\nfunction getText (stackItem) {\n if (!(\n stackItem instanceof BlockStackItem\n || stackItem instanceof TableCellStackItem\n )) {\n throw new Error('Only blocks and table cells can be requested for text contents.');\n }\n return (stackItem.inlineTextBuilder.isEmpty())\n ? stackItem.rawText\n : stackItem.rawText + stackItem.inlineTextBuilder.toString();\n}\n\nfunction addText (stackItem, text, leadingLineBreaks, trailingLineBreaks) {\n if (!(\n stackItem instanceof BlockStackItem\n || stackItem instanceof TableCellStackItem\n )) {\n throw new Error('Only blocks and table cells can contain text.');\n }\n const parentText = getText(stackItem);\n const lineBreaks = Math.max(stackItem.stashedLineBreaks, leadingLineBreaks);\n stackItem.inlineTextBuilder.clear();\n if (parentText) {\n stackItem.rawText = parentText + '\\n'.repeat(lineBreaks) + text;\n } else {\n stackItem.rawText = text;\n stackItem.leadingLineBreaks = lineBreaks;\n }\n stackItem.stashedLineBreaks = trailingLineBreaks;\n}\n\nmodule.exports = { BlockTextBuilder: BlockTextBuilder };\n\n\n//# sourceURL=webpack://dcp/./node_modules/html-to-text/lib/block-text-builder.js?");
|
|
2545
2578
|
|
|
2546
2579
|
/***/ }),
|
|
2547
2580
|
|
|
@@ -2551,7 +2584,7 @@ eval("\nconst { trimCharacter } = __webpack_require__(/*! ./helper */ \"./node_m
|
|
|
2551
2584
|
\****************************************************/
|
|
2552
2585
|
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
2553
2586
|
|
|
2554
|
-
eval("const he = __webpack_require__(/*! he */ \"./node_modules/he/he.js\");\n\nconst { get, numberToLetterSequence, numberToRoman, splitClassesAndIds, trimCharacter } = __webpack_require__(/*! ./helper */ \"./node_modules/html-to-text/lib/helper.js\");\n\n// eslint-disable-next-line import/no-unassigned-import\n__webpack_require__(/*! ./typedefs */ \"./node_modules/html-to-text/lib/typedefs.js\");\n\n\n/**\n * Dummy formatter that discards the input and does nothing.\n *\n * @type { FormatCallback }\n */\nfunction formatSkip (elem, walk, builder, formatOptions) {\n /* do nothing */\n}\n\n/**\n * Process an inline-level element.\n *\n * @type { FormatCallback }\n */\nfunction formatInline (elem, walk, builder, formatOptions) {\n walk(elem.children, builder);\n}\n\n/**\n * Process a block-level container.\n *\n * @type { FormatCallback }\n */\nfunction formatBlock (elem, walk, builder, formatOptions) {\n builder.openBlock({ leadingLineBreaks: formatOptions.leadingLineBreaks });\n walk(elem.children, builder);\n builder.closeBlock({ trailingLineBreaks: formatOptions.trailingLineBreaks });\n}\n\n/**\n * Process a line-break.\n *\n * @type { FormatCallback }\n */\nfunction formatLineBreak (elem, walk, builder, formatOptions) {\n builder.addLineBreak();\n}\n\n/**\n * Process a `wbk` tag (word break opportunity).\n *\n * @type { FormatCallback }\n */\nfunction formatWbr (elem, walk, builder, formatOptions) {\n builder.addWordBreakOpportunity();\n}\n\n/**\n * Process a horizontal line.\n *\n * @type { FormatCallback }\n */\nfunction formatHorizontalLine (elem, walk, builder, formatOptions) {\n builder.openBlock({ leadingLineBreaks: formatOptions.leadingLineBreaks || 2 });\n builder.addInline('-'.repeat(formatOptions.length || builder.options.wordwrap || 40));\n builder.closeBlock({ trailingLineBreaks: formatOptions.trailingLineBreaks || 2 });\n}\n\n/**\n * Process a paragraph.\n *\n * @type { FormatCallback }\n */\nfunction formatParagraph (elem, walk, builder, formatOptions) {\n builder.openBlock({ leadingLineBreaks: formatOptions.leadingLineBreaks || 2 });\n walk(elem.children, builder);\n builder.closeBlock({ trailingLineBreaks: formatOptions.trailingLineBreaks || 2 });\n}\n\n/**\n * Process a preformatted content.\n *\n * @type { FormatCallback }\n */\nfunction formatPre (elem, walk, builder, formatOptions) {\n builder.openBlock({\n isPre: true,\n leadingLineBreaks: formatOptions.leadingLineBreaks || 2\n });\n walk(elem.children, builder);\n builder.closeBlock({ trailingLineBreaks: formatOptions.trailingLineBreaks || 2 });\n}\n\n/**\n * Process a heading.\n *\n * @type { FormatCallback }\n */\nfunction formatHeading (elem, walk, builder, formatOptions) {\n builder.openBlock({ leadingLineBreaks: formatOptions.leadingLineBreaks || 2 });\n if (formatOptions.uppercase !== false) {\n builder.pushWordTransform(str => str.toUpperCase());\n walk(elem.children, builder);\n builder.popWordTransform();\n } else {\n walk(elem.children, builder);\n }\n builder.closeBlock({ trailingLineBreaks: formatOptions.trailingLineBreaks || 2 });\n}\n\n/**\n * Process a blockquote.\n *\n * @type { FormatCallback }\n */\nfunction formatBlockquote (elem, walk, builder, formatOptions) {\n builder.openBlock({\n leadingLineBreaks: formatOptions.leadingLineBreaks || 2,\n reservedLineLength: 2\n });\n walk(elem.children, builder);\n builder.closeBlock({\n trailingLineBreaks: formatOptions.trailingLineBreaks || 2,\n blockTransform: str => ((formatOptions.trimEmptyLines !== false) ? trimCharacter(str, '\\n') : str)\n .split('\\n')\n .map(line => '> ' + line)\n .join('\\n')\n });\n}\n\n/**\n * Process an image.\n *\n * @type { FormatCallback }\n */\nfunction formatImage (elem, walk, builder, formatOptions) {\n const attribs = elem.attribs || {};\n const alt = (attribs.alt)\n ? he.decode(attribs.alt, builder.options.decodeOptions)\n : '';\n const src = (!attribs.src)\n ? ''\n : (formatOptions.baseUrl && attribs.src.indexOf('/') === 0)\n ? formatOptions.baseUrl + attribs.src\n : attribs.src;\n const text = (!src)\n ? alt\n : (!alt)\n ? '[' + src + ']'\n : alt + ' [' + src + ']';\n\n builder.addInline(text);\n}\n\n/**\n * Process an anchor.\n *\n * @type { FormatCallback }\n */\nfunction formatAnchor (elem, walk, builder, formatOptions) {\n function getHref () {\n if (formatOptions.ignoreHref) { return ''; }\n if (!elem.attribs || !elem.attribs.href) { return ''; }\n let href = elem.attribs.href.replace(/^mailto:/, '');\n if (formatOptions.noAnchorUrl && href[0] === '#') { return ''; }\n href = (formatOptions.baseUrl && href[0] === '/')\n ? formatOptions.baseUrl + href\n : href;\n return he.decode(href, builder.options.decodeOptions);\n }\n const href = getHref();\n if (!href) {\n walk(elem.children, builder);\n } else {\n let text = '';\n builder.pushWordTransform(\n str => {\n if (str) { text += str; }\n return str;\n }\n );\n walk(elem.children, builder);\n builder.popWordTransform();\n\n const hideSameLink = formatOptions.hideLinkHrefIfSameAsText && href === text;\n if (!hideSameLink) {\n builder.addInline(\n (!text)\n ? href\n : (formatOptions.noLinkBrackets)\n ? ' ' + href\n : ' [' + href + ']',\n { noWordTransform: true }\n );\n }\n }\n}\n\n/**\n * @param { DomNode } elem List items with their prefixes.\n * @param { RecursiveCallback } walk Recursive callback to process child nodes.\n * @param { BlockTextBuilder } builder Passed around to accumulate output text.\n * @param { FormatOptions } formatOptions Options specific to a formatter.\n * @param { () => string } nextPrefixCallback Function that returns inreasing index each time it is called.\n */\nfunction formatList (elem, walk, builder, formatOptions, nextPrefixCallback) {\n const isNestedList = get(elem, ['parent', 'name']) === 'li';\n\n // With Roman numbers, index length is not as straightforward as with Arabic numbers or letters,\n // so the dumb length comparison is the most robust way to get the correct value.\n let maxPrefixLength = 0;\n const listItems = (elem.children || [])\n // it might be more accuurate to check only for html spaces here, but no significant benefit\n .filter(child => child.type !== 'text' || !/^\\s*$/.test(child.data))\n .map(function (child) {\n if (child.name !== 'li') {\n return { node: child, prefix: '' };\n }\n const prefix = (isNestedList)\n ? nextPrefixCallback().trimStart()\n : nextPrefixCallback();\n if (prefix.length > maxPrefixLength) { maxPrefixLength = prefix.length; }\n return { node: child, prefix: prefix };\n });\n if (!listItems.length) { return; }\n\n const reservedLineLength = maxPrefixLength;\n const spacing = '\\n' + ' '.repeat(reservedLineLength);\n builder.openBlock({ leadingLineBreaks: isNestedList ? 1 : (formatOptions.leadingLineBreaks || 2) });\n for (const { node, prefix } of listItems) {\n builder.openBlock({\n leadingLineBreaks: 1,\n reservedLineLength: reservedLineLength\n });\n walk([node], builder);\n builder.closeBlock({\n trailingLineBreaks: 1,\n blockTransform: str => prefix + ' '.repeat(reservedLineLength - prefix.length) + str.replace(/\\n/g, spacing)\n });\n }\n builder.closeBlock({ trailingLineBreaks: isNestedList ? 1 : (formatOptions.trailingLineBreaks || 2) });\n}\n\n/**\n * Process an unordered list.\n *\n * @type { FormatCallback }\n */\nfunction formatUnorderedList (elem, walk, builder, formatOptions) {\n const prefix = formatOptions.itemPrefix || ' * ';\n return formatList(elem, walk, builder, formatOptions, () => prefix);\n}\n\n/**\n * Process an ordered list.\n *\n * @type { FormatCallback }\n */\nfunction formatOrderedList (elem, walk, builder, formatOptions) {\n let nextIndex = Number(elem.attribs.start || '1');\n const indexFunction = getOrderedListIndexFunction(elem.attribs.type);\n const nextPrefixCallback = () => ' ' + indexFunction(nextIndex++) + '. ';\n return formatList(elem, walk, builder, formatOptions, nextPrefixCallback);\n}\n\n/**\n * Return a function that can be used to generate index markers of a specified format.\n *\n * @param { string } [olType='1'] Marker type.\n * @returns { (i: number) => string }\n */\nfunction getOrderedListIndexFunction (olType = '1') {\n switch (olType) {\n case 'a': return (i) => numberToLetterSequence(i, 'a');\n case 'A': return (i) => numberToLetterSequence(i, 'A');\n case 'i': return (i) => numberToRoman(i).toLowerCase();\n case 'I': return (i) => numberToRoman(i);\n case '1':\n default: return (i) => (i).toString();\n }\n}\n\nfunction isDataTable (attr, tables) {\n if (tables === true) { return true; }\n if (!attr) { return false; }\n\n const { classes, ids } = splitClassesAndIds(tables);\n const attrClasses = (attr['class'] || '').split(' ');\n const attrIds = (attr['id'] || '').split(' ');\n\n return attrClasses.some(x => classes.includes(x)) || attrIds.some(x => ids.includes(x));\n}\n\n/**\n * Process a table (either as a container or as a data table, depending on options).\n *\n * @type { FormatCallback }\n */\nfunction formatTable (elem, walk, builder, formatOptions) {\n return isDataTable(elem.attribs, builder.options.tables)\n ? formatDataTable(elem, walk, builder, formatOptions)\n : formatBlock(elem, walk, builder, formatOptions);\n}\n\n/**\n * Process a data table.\n *\n * @type { FormatCallback }\n */\nfunction formatDataTable (elem, walk, builder, formatOptions) {\n builder.openTable();\n elem.children.forEach(walkTable);\n builder.closeTable({\n colSpacing: formatOptions.colSpacing,\n leadingLineBreaks: formatOptions.leadingLineBreaks,\n rowSpacing: formatOptions.rowSpacing,\n trailingLineBreaks: formatOptions.trailingLineBreaks\n });\n\n function formatCell (cellNode) {\n const colspan = +get(cellNode, ['attribs', 'colspan']) || 1;\n const rowspan = +get(cellNode, ['attribs', 'rowspan']) || 1;\n builder.openTableCell({ maxColumnWidth: formatOptions.maxColumnWidth });\n walk(cellNode.children, builder);\n builder.closeTableCell({ colspan: colspan, rowspan: rowspan });\n }\n\n function walkTable (elem) {\n if (elem.type !== 'tag') { return; }\n\n const formatHeaderCell = (formatOptions.uppercaseHeaderCells)\n ? (cellNode) => {\n builder.pushWordTransform(str => str.toUpperCase());\n formatCell(cellNode);\n builder.popWordTransform();\n }\n : formatCell;\n\n switch (elem.name) {\n case 'thead':\n case 'tbody':\n case 'tfoot':\n case 'center':\n elem.children.forEach(walkTable);\n return;\n\n case 'tr': {\n builder.openTableRow();\n for (const childOfTr of elem.children) {\n if (childOfTr.type !== 'tag') { continue; }\n switch (childOfTr.name) {\n case 'th': {\n formatHeaderCell(childOfTr);\n break;\n }\n case 'td': {\n formatCell(childOfTr);\n break;\n }\n default:\n // do nothing\n }\n }\n builder.closeTableRow();\n break;\n }\n\n default:\n // do nothing\n }\n }\n}\n\nmodule.exports = {\n anchor: formatAnchor,\n block: formatBlock,\n blockquote: formatBlockquote,\n dataTable: formatDataTable,\n heading: formatHeading,\n horizontalLine: formatHorizontalLine,\n image: formatImage,\n inline: formatInline,\n lineBreak: formatLineBreak,\n orderedList: formatOrderedList,\n paragraph: formatParagraph,\n pre: formatPre,\n skip: formatSkip,\n table: formatTable,\n unorderedList: formatUnorderedList,\n wbr: formatWbr\n};\n\n\n//# sourceURL=webpack://dcp/./node_modules/html-to-text/lib/formatter.js?");
|
|
2587
|
+
eval("const he = __webpack_require__(/*! he */ \"./node_modules/he/he.js\");\n\nconst { get, numberToLetterSequence, numberToRoman, splitClassesAndIds, trimCharacter } = __webpack_require__(/*! ./helper */ \"./node_modules/html-to-text/lib/helper.js\");\n\n// eslint-disable-next-line import/no-unassigned-import\n__webpack_require__(/*! ./typedefs */ \"./node_modules/html-to-text/lib/typedefs.js\");\n\n\n/**\n * Dummy formatter that discards the input and does nothing.\n *\n * @type { FormatCallback }\n */\nfunction formatSkip (elem, walk, builder, formatOptions) {\n /* do nothing */\n}\n\n/**\n * Process an inline-level element.\n *\n * @type { FormatCallback }\n */\nfunction formatInline (elem, walk, builder, formatOptions) {\n walk(elem.children, builder);\n}\n\n/**\n * Process a block-level container.\n *\n * @type { FormatCallback }\n */\nfunction formatBlock (elem, walk, builder, formatOptions) {\n builder.openBlock({ leadingLineBreaks: formatOptions.leadingLineBreaks });\n walk(elem.children, builder);\n builder.closeBlock({ trailingLineBreaks: formatOptions.trailingLineBreaks });\n}\n\n/**\n * Process a line-break.\n *\n * @type { FormatCallback }\n */\nfunction formatLineBreak (elem, walk, builder, formatOptions) {\n builder.addLineBreak();\n}\n\n/**\n * Process a `wbk` tag (word break opportunity).\n *\n * @type { FormatCallback }\n */\nfunction formatWbr (elem, walk, builder, formatOptions) {\n builder.addWordBreakOpportunity();\n}\n\n/**\n * Process a horizontal line.\n *\n * @type { FormatCallback }\n */\nfunction formatHorizontalLine (elem, walk, builder, formatOptions) {\n builder.openBlock({ leadingLineBreaks: formatOptions.leadingLineBreaks || 2 });\n builder.addInline('-'.repeat(formatOptions.length || builder.options.wordwrap || 40));\n builder.closeBlock({ trailingLineBreaks: formatOptions.trailingLineBreaks || 2 });\n}\n\n/**\n * Process a paragraph.\n *\n * @type { FormatCallback }\n */\nfunction formatParagraph (elem, walk, builder, formatOptions) {\n builder.openBlock({ leadingLineBreaks: formatOptions.leadingLineBreaks || 2 });\n walk(elem.children, builder);\n builder.closeBlock({ trailingLineBreaks: formatOptions.trailingLineBreaks || 2 });\n}\n\n/**\n * Process a preformatted content.\n *\n * @type { FormatCallback }\n */\nfunction formatPre (elem, walk, builder, formatOptions) {\n builder.openBlock({\n isPre: true,\n leadingLineBreaks: formatOptions.leadingLineBreaks || 2\n });\n walk(elem.children, builder);\n builder.closeBlock({ trailingLineBreaks: formatOptions.trailingLineBreaks || 2 });\n}\n\n/**\n * Process a heading.\n *\n * @type { FormatCallback }\n */\nfunction formatHeading (elem, walk, builder, formatOptions) {\n builder.openBlock({ leadingLineBreaks: formatOptions.leadingLineBreaks || 2 });\n if (formatOptions.uppercase !== false) {\n builder.pushWordTransform(str => str.toUpperCase());\n walk(elem.children, builder);\n builder.popWordTransform();\n } else {\n walk(elem.children, builder);\n }\n builder.closeBlock({ trailingLineBreaks: formatOptions.trailingLineBreaks || 2 });\n}\n\n/**\n * Process a blockquote.\n *\n * @type { FormatCallback }\n */\nfunction formatBlockquote (elem, walk, builder, formatOptions) {\n builder.openBlock({\n leadingLineBreaks: formatOptions.leadingLineBreaks || 2,\n reservedLineLength: 2\n });\n walk(elem.children, builder);\n builder.closeBlock({\n trailingLineBreaks: formatOptions.trailingLineBreaks || 2,\n blockTransform: str => ((formatOptions.trimEmptyLines !== false) ? trimCharacter(str, '\\n') : str)\n .split('\\n')\n .map(line => '> ' + line)\n .join('\\n')\n });\n}\n\nfunction withBrackets (str, brackets) {\n if (!brackets) { return str; }\n\n const lbr = (typeof brackets[0] === 'string')\n ? brackets[0]\n : '[';\n const rbr = (typeof brackets[1] === 'string')\n ? brackets[1]\n : ']';\n return lbr + str + rbr;\n}\n\n/**\n * Process an image.\n *\n * @type { FormatCallback }\n */\nfunction formatImage (elem, walk, builder, formatOptions) {\n const attribs = elem.attribs || {};\n const alt = (attribs.alt)\n ? he.decode(attribs.alt, builder.options.decodeOptions)\n : '';\n const src = (!attribs.src)\n ? ''\n : (formatOptions.baseUrl && attribs.src.indexOf('/') === 0)\n ? formatOptions.baseUrl + attribs.src\n : attribs.src;\n const text = (!src)\n ? alt\n : (!alt)\n ? withBrackets(src, formatOptions.linkBrackets)\n : alt + ' ' + withBrackets(src, formatOptions.linkBrackets);\n\n builder.addInline(text);\n}\n\n/**\n * Process an anchor.\n *\n * @type { FormatCallback }\n */\nfunction formatAnchor (elem, walk, builder, formatOptions) {\n function getHref () {\n if (formatOptions.ignoreHref) { return ''; }\n if (!elem.attribs || !elem.attribs.href) { return ''; }\n let href = elem.attribs.href.replace(/^mailto:/, '');\n if (formatOptions.noAnchorUrl && href[0] === '#') { return ''; }\n href = (formatOptions.baseUrl && href[0] === '/')\n ? formatOptions.baseUrl + href\n : href;\n return he.decode(href, builder.options.decodeOptions);\n }\n const href = getHref();\n if (!href) {\n walk(elem.children, builder);\n } else {\n let text = '';\n builder.pushWordTransform(\n str => {\n if (str) { text += str; }\n return str;\n }\n );\n walk(elem.children, builder);\n builder.popWordTransform();\n\n const hideSameLink = formatOptions.hideLinkHrefIfSameAsText && href === text;\n if (!hideSameLink) {\n builder.addInline(\n (!text)\n ? href\n : ' ' + withBrackets(href, formatOptions.linkBrackets),\n { noWordTransform: true }\n );\n }\n }\n}\n\n/**\n * @param { DomNode } elem List items with their prefixes.\n * @param { RecursiveCallback } walk Recursive callback to process child nodes.\n * @param { BlockTextBuilder } builder Passed around to accumulate output text.\n * @param { FormatOptions } formatOptions Options specific to a formatter.\n * @param { () => string } nextPrefixCallback Function that returns increasing index each time it is called.\n */\nfunction formatList (elem, walk, builder, formatOptions, nextPrefixCallback) {\n const isNestedList = get(elem, ['parent', 'name']) === 'li';\n\n // With Roman numbers, index length is not as straightforward as with Arabic numbers or letters,\n // so the dumb length comparison is the most robust way to get the correct value.\n let maxPrefixLength = 0;\n const listItems = (elem.children || [])\n // it might be more accurate to check only for html spaces here, but no significant benefit\n .filter(child => child.type !== 'text' || !/^\\s*$/.test(child.data))\n .map(function (child) {\n if (child.name !== 'li') {\n return { node: child, prefix: '' };\n }\n const prefix = (isNestedList)\n ? nextPrefixCallback().trimStart()\n : nextPrefixCallback();\n if (prefix.length > maxPrefixLength) { maxPrefixLength = prefix.length; }\n return { node: child, prefix: prefix };\n });\n if (!listItems.length) { return; }\n\n const reservedLineLength = maxPrefixLength;\n const spacing = '\\n' + ' '.repeat(reservedLineLength);\n builder.openBlock({ leadingLineBreaks: isNestedList ? 1 : (formatOptions.leadingLineBreaks || 2) });\n for (const { node, prefix } of listItems) {\n builder.openBlock({\n leadingLineBreaks: 1,\n reservedLineLength: reservedLineLength\n });\n walk([node], builder);\n builder.closeBlock({\n trailingLineBreaks: 1,\n blockTransform: str => prefix + ' '.repeat(reservedLineLength - prefix.length) + str.replace(/\\n/g, spacing)\n });\n }\n builder.closeBlock({ trailingLineBreaks: isNestedList ? 1 : (formatOptions.trailingLineBreaks || 2) });\n}\n\n/**\n * Process an unordered list.\n *\n * @type { FormatCallback }\n */\nfunction formatUnorderedList (elem, walk, builder, formatOptions) {\n const prefix = formatOptions.itemPrefix || ' * ';\n return formatList(elem, walk, builder, formatOptions, () => prefix);\n}\n\n/**\n * Process an ordered list.\n *\n * @type { FormatCallback }\n */\nfunction formatOrderedList (elem, walk, builder, formatOptions) {\n let nextIndex = Number(elem.attribs.start || '1');\n const indexFunction = getOrderedListIndexFunction(elem.attribs.type);\n const nextPrefixCallback = () => ' ' + indexFunction(nextIndex++) + '. ';\n return formatList(elem, walk, builder, formatOptions, nextPrefixCallback);\n}\n\n/**\n * Return a function that can be used to generate index markers of a specified format.\n *\n * @param { string } [olType='1'] Marker type.\n * @returns { (i: number) => string }\n */\nfunction getOrderedListIndexFunction (olType = '1') {\n switch (olType) {\n case 'a': return (i) => numberToLetterSequence(i, 'a');\n case 'A': return (i) => numberToLetterSequence(i, 'A');\n case 'i': return (i) => numberToRoman(i).toLowerCase();\n case 'I': return (i) => numberToRoman(i);\n case '1':\n default: return (i) => (i).toString();\n }\n}\n\nfunction isDataTable (attr, tables) {\n if (tables === true) { return true; }\n if (!attr) { return false; }\n\n const { classes, ids } = splitClassesAndIds(tables);\n const attrClasses = (attr['class'] || '').split(' ');\n const attrIds = (attr['id'] || '').split(' ');\n\n return attrClasses.some(x => classes.includes(x)) || attrIds.some(x => ids.includes(x));\n}\n\n/**\n * Process a table (either as a container or as a data table, depending on options).\n *\n * @type { FormatCallback }\n */\nfunction formatTable (elem, walk, builder, formatOptions) {\n return isDataTable(elem.attribs, builder.options.tables)\n ? formatDataTable(elem, walk, builder, formatOptions)\n : formatBlock(elem, walk, builder, formatOptions);\n}\n\n/**\n * Process a data table.\n *\n * @type { FormatCallback }\n */\nfunction formatDataTable (elem, walk, builder, formatOptions) {\n builder.openTable();\n elem.children.forEach(walkTable);\n builder.closeTable({\n colSpacing: formatOptions.colSpacing,\n leadingLineBreaks: formatOptions.leadingLineBreaks,\n rowSpacing: formatOptions.rowSpacing,\n trailingLineBreaks: formatOptions.trailingLineBreaks\n });\n\n function formatCell (cellNode) {\n const colspan = +get(cellNode, ['attribs', 'colspan']) || 1;\n const rowspan = +get(cellNode, ['attribs', 'rowspan']) || 1;\n builder.openTableCell({ maxColumnWidth: formatOptions.maxColumnWidth });\n walk(cellNode.children, builder);\n builder.closeTableCell({ colspan: colspan, rowspan: rowspan });\n }\n\n function walkTable (elem) {\n if (elem.type !== 'tag') { return; }\n\n const formatHeaderCell = (formatOptions.uppercaseHeaderCells !== false)\n ? (cellNode) => {\n builder.pushWordTransform(str => str.toUpperCase());\n formatCell(cellNode);\n builder.popWordTransform();\n }\n : formatCell;\n\n switch (elem.name) {\n case 'thead':\n case 'tbody':\n case 'tfoot':\n case 'center':\n elem.children.forEach(walkTable);\n return;\n\n case 'tr': {\n builder.openTableRow();\n for (const childOfTr of elem.children) {\n if (childOfTr.type !== 'tag') { continue; }\n switch (childOfTr.name) {\n case 'th': {\n formatHeaderCell(childOfTr);\n break;\n }\n case 'td': {\n formatCell(childOfTr);\n break;\n }\n default:\n // do nothing\n }\n }\n builder.closeTableRow();\n break;\n }\n\n default:\n // do nothing\n }\n }\n}\n\nmodule.exports = {\n anchor: formatAnchor,\n block: formatBlock,\n blockquote: formatBlockquote,\n dataTable: formatDataTable,\n heading: formatHeading,\n horizontalLine: formatHorizontalLine,\n image: formatImage,\n inline: formatInline,\n lineBreak: formatLineBreak,\n orderedList: formatOrderedList,\n paragraph: formatParagraph,\n pre: formatPre,\n skip: formatSkip,\n table: formatTable,\n unorderedList: formatUnorderedList,\n wbr: formatWbr\n};\n\n\n//# sourceURL=webpack://dcp/./node_modules/html-to-text/lib/formatter.js?");
|
|
2555
2588
|
|
|
2556
2589
|
/***/ }),
|
|
2557
2590
|
|
|
@@ -2559,9 +2592,9 @@ eval("const he = __webpack_require__(/*! he */ \"./node_modules/he/he.js\");\n\n
|
|
|
2559
2592
|
/*!*************************************************!*\
|
|
2560
2593
|
!*** ./node_modules/html-to-text/lib/helper.js ***!
|
|
2561
2594
|
\*************************************************/
|
|
2562
|
-
/***/ ((module) => {
|
|
2595
|
+
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
2563
2596
|
|
|
2564
|
-
eval("\
|
|
2597
|
+
eval("\nconst merge = __webpack_require__(/*! deepmerge */ \"./node_modules/deepmerge/dist/cjs.js\");\n\n/**\n * Given a list of class and ID selectors (prefixed with '.' and '#'),\n * return them as separate lists of names without prefixes.\n *\n * @param { string[] } selectors Class and ID selectors (`[\".class\", \"#id\"]` etc).\n * @returns { { classes: string[], ids: string[] } }\n */\nfunction splitClassesAndIds (selectors) {\n const classes = [];\n const ids = [];\n for (const selector of selectors) {\n if (selector.startsWith('.')) {\n classes.push(selector.substring(1));\n } else if (selector.startsWith('#')) {\n ids.push(selector.substring(1));\n }\n }\n return { classes: classes, ids: ids };\n}\n\n/**\n * Make a recursive function that will only run to a given depth\n * and switches to an alternative function at that depth. \\\n * No limitation if `n` is `undefined` (Just wraps `f` in that case).\n *\n * @param { number | undefined } n Allowed depth of recursion. `undefined` for no limitation.\n * @param { Function } f Function that accepts recursive callback as the first argument.\n * @param { Function } [g] Function to run instead, when maximum depth was reached. Do nothing by default.\n * @returns { Function }\n */\nfunction limitedDepthRecursive (n, f, g = () => undefined) {\n if (n === undefined) {\n const f1 = function (...args) { return f(f1, ...args); };\n return f1;\n }\n if (n >= 0) {\n return function (...args) { return f(limitedDepthRecursive(n - 1, f, g), ...args); };\n }\n return g;\n}\n\n/**\n * Convert a number into alphabetic sequence representation (Sequence without zeroes).\n *\n * For example: `a, ..., z, aa, ..., zz, aaa, ...`.\n *\n * @param { number } num Number to convert. Must be >= 1.\n * @param { string } [baseChar = 'a'] Character for 1 in the sequence.\n * @param { number } [base = 26] Number of characters in the sequence.\n * @returns { string }\n */\nfunction numberToLetterSequence (num, baseChar = 'a', base = 26) {\n const digits = [];\n do {\n num -= 1;\n digits.push(num % base);\n num = (num / base) >> 0; // quick `floor`\n } while (num > 0);\n const baseCode = baseChar.charCodeAt(0);\n return digits\n .reverse()\n .map(n => String.fromCharCode(baseCode + n))\n .join('');\n}\n\nconst I = ['I', 'X', 'C', 'M'];\nconst V = ['V', 'L', 'D'];\n\n/**\n * Convert a number to it's Roman representation. No large numbers extension.\n *\n * @param { number } num Number to convert. `0 < num <= 3999`.\n * @returns { string }\n */\nfunction numberToRoman (num) {\n return [...(num) + '']\n .map(n => +n)\n .reverse()\n .map((v, i) => ((v % 5 < 4)\n ? (v < 5 ? '' : V[i]) + I[i].repeat(v % 5)\n : I[i] + (v < 5 ? V[i] : I[i + 1])))\n .reverse()\n .join('');\n}\n\n/**\n * Return the same string or a substring with the given character occurences removed from each end if any.\n *\n * @param { string } str A string to trim.\n * @param { string } char A character to be trimmed.\n * @returns { string }\n */\nfunction trimCharacter (str, char) {\n let start = 0;\n let end = str.length;\n while (start < end && str[start] === char) { ++start; }\n while (end > start && str[end - 1] === char) { --end; }\n return (start > 0 || end < str.length)\n ? str.substring(start, end)\n : str;\n}\n\n/**\n * Get a nested property from an object.\n *\n * @param { object } obj The object to query for the value.\n * @param { string[] } path The path to the property.\n * @returns { any }\n */\nfunction get (obj, path) {\n for (const key of path) {\n if (!obj) { return undefined; }\n obj = obj[key];\n }\n return obj;\n}\n\n/**\n * Deduplicate an array by a given key callback.\n * Item properties are merged recursively and with the preference for last defined values.\n * Of items with the same key, merged item takes the place of the last item,\n * others are omitted.\n *\n * @param { any[] } items An array to deduplicate.\n * @param { (x: any) => string } getKey Callback to get a value that distinguishes unique items.\n * @returns { any[] }\n */\nfunction mergeDuplicatesPreferLast (items, getKey) {\n const map = new Map();\n for (let i = items.length; i-- > 0;) {\n const item = items[i];\n const key = getKey(item);\n map.set(\n key,\n (map.has(key))\n ? merge(item, map.get(key), { arrayMerge: overwriteMerge })\n : item\n );\n }\n return [...map.values()].reverse();\n}\n\nconst overwriteMerge = (acc, src, options) => [...src];\n\nmodule.exports = {\n get: get,\n limitedDepthRecursive: limitedDepthRecursive,\n mergeDuplicatesPreferLast: mergeDuplicatesPreferLast,\n numberToLetterSequence: numberToLetterSequence,\n numberToRoman: numberToRoman,\n splitClassesAndIds: splitClassesAndIds,\n trimCharacter: trimCharacter\n};\n\n\n//# sourceURL=webpack://dcp/./node_modules/html-to-text/lib/helper.js?");
|
|
2565
2598
|
|
|
2566
2599
|
/***/ }),
|
|
2567
2600
|
|
|
@@ -2571,7 +2604,7 @@ eval("\n/**\n * Split given tag selector into it's components.\n * Only element
|
|
|
2571
2604
|
\*******************************************************/
|
|
2572
2605
|
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
2573
2606
|
|
|
2574
|
-
eval("const merge = __webpack_require__(/*! deepmerge */ \"./node_modules/deepmerge/dist/cjs.js\");\nconst he = __webpack_require__(/*! he */ \"./node_modules/he/he.js\");\nconst htmlparser = __webpack_require__(/*! htmlparser2 */ \"./node_modules/html-to-text/node_modules/htmlparser2/lib/index.js\");\n\nconst { BlockTextBuilder } = __webpack_require__(/*! ./block-text-builder */ \"./node_modules/html-to-text/lib/block-text-builder.js\");\nconst defaultFormatters = __webpack_require__(/*! ./formatter */ \"./node_modules/html-to-text/lib/formatter.js\");\nconst { limitedDepthRecursive, set, splitSelector } = __webpack_require__(/*! ./helper */ \"./node_modules/html-to-text/lib/helper.js\");\n\n// eslint-disable-next-line import/no-unassigned-import\n__webpack_require__(/*! ./typedefs */ \"./node_modules/html-to-text/lib/typedefs.js\");\n\n\n/**\n * Default options.\n *\n * @constant\n * @type { Options }\n * @default\n * @private\n */\nconst DEFAULT_OPTIONS = {\n baseElement: 'body',\n decodeOptions: {\n isAttributeValue: false,\n strict: false\n },\n formatters: {},\n limits: {\n ellipsis: '...',\n maxChildNodes: undefined,\n maxDepth: undefined,\n maxInputLength: (1 << 24) // 16_777_216\n },\n longWordSplit: {\n forceWrapOnLimit: false,\n wrapCharacters: []\n },\n preserveNewlines: false,\n returnDomByDefault: true,\n tables: [],\n tags: {\n '': { format: 'inline' }, // defaults for any other tag name\n 'a': {\n format: 'anchor',\n options: { baseUrl: null, hideLinkHrefIfSameAsText: false, ignoreHref: false, noAnchorUrl: true, noLinkBrackets: false }\n },\n 'article': { format: 'block' },\n 'aside': { format: 'block' },\n 'blockquote': {\n format: 'blockquote',\n options: { leadingLineBreaks: 2, trailingLineBreaks: 2, trimEmptyLines: true }\n },\n 'br': { format: 'lineBreak' },\n 'div': { format: 'block' },\n 'footer': { format: 'block' },\n 'form': { format: 'block' },\n 'h1': { format: 'heading', options: { leadingLineBreaks: 3, trailingLineBreaks: 2, uppercase: true } },\n 'h2': { format: 'heading', options: { leadingLineBreaks: 3, trailingLineBreaks: 2, uppercase: true } },\n 'h3': { format: 'heading', options: { leadingLineBreaks: 3, trailingLineBreaks: 2, uppercase: true } },\n 'h4': { format: 'heading', options: { leadingLineBreaks: 2, trailingLineBreaks: 2, uppercase: true } },\n 'h5': { format: 'heading', options: { leadingLineBreaks: 2, trailingLineBreaks: 2, uppercase: true } },\n 'h6': { format: 'heading', options: { leadingLineBreaks: 2, trailingLineBreaks: 2, uppercase: true } },\n 'header': { format: 'block' },\n 'hr': { format: 'horizontalLine', options: { leadingLineBreaks: 2, length: undefined, trailingLineBreaks: 2 } },\n 'img': { format: 'image', options: { baseUrl: null } },\n 'main': { format: 'block' },\n 'nav': { format: 'block' },\n 'ol': { format: 'orderedList', options: { leadingLineBreaks: 2, trailingLineBreaks: 2 } },\n 'p': { format: 'paragraph', options: { leadingLineBreaks: 2, trailingLineBreaks: 2 } },\n 'pre': { format: 'pre', options: { leadingLineBreaks: 2, trailingLineBreaks: 2 } },\n 'section': { format: 'block' },\n 'table': {\n format: 'table',\n options: {\n colSpacing: 3,\n leadingLineBreaks: 2,\n maxColumnWidth: 60,\n rowSpacing: 0,\n trailingLineBreaks: 2,\n uppercaseHeaderCells: true\n }\n },\n 'ul': {\n format: 'unorderedList',\n options: { itemPrefix: ' * ', leadingLineBreaks: 2, trailingLineBreaks: 2 }\n },\n 'wbr': { format: 'wbr' }\n },\n whitespaceCharacters: ' \\t\\r\\n\\f\\u200b',\n wordwrap: 80\n};\n\n/**\n * Convert given HTML content to plain text string.\n *\n * @param { string } html HTML content to convert.\n * @param { Options } [options = {}] HtmlToText options.\n * @returns { string } Plain text string.\n * @static\n *\n * @example\n * const { htmlToText } = require('html-to-text');\n * const text = htmlToText('<h1>Hello World</h1>', {\n * wordwrap: 130\n * });\n * console.log(text); // HELLO WORLD\n */\nfunction htmlToText (html, options = {}) {\n options = merge(\n DEFAULT_OPTIONS,\n options,\n { arrayMerge: (destinationArray, sourceArray, mergeOptions) => sourceArray }\n );\n options.formatters = Object.assign({}, defaultFormatters, options.formatters);\n\n handleDeprecatedOptions(options);\n\n const maxInputLength = options.limits.maxInputLength;\n if (maxInputLength && html && html.length > maxInputLength) {\n console.warn(\n `Input length ${html.length} is above allowed limit of ${maxInputLength}. Truncating without ellipsis.`\n );\n html = html.substring(0, maxInputLength);\n }\n\n const handler = new htmlparser.DefaultHandler();\n new htmlparser.Parser(handler, { decodeEntities: false }).parseComplete(html);\n\n const limitedWalk = limitedDepthRecursive(\n options.limits.maxDepth,\n recursiveWalk,\n function (dom, builder) {\n builder.addInline(options.limits.ellipsis || '');\n }\n );\n\n const baseElements = Array.isArray(options.baseElement)\n ? options.baseElement\n : [options.baseElement];\n const bases = baseElements\n .map(be => findBase(handler.dom, options, be))\n .filter(b => b)\n .reduce((acc, b) => acc.concat(b), []);\n\n const builder = new BlockTextBuilder(options);\n limitedWalk(bases, builder);\n return builder.toString();\n}\n\n/**\n * Map previously existing and now deprecated options to the new options layout.\n * This is a subject for cleanup in major releases.\n *\n * @param { Options } options HtmlToText options.\n */\nfunction handleDeprecatedOptions (options) {\n const tagDefinitions = Object.values(options.tags);\n\n function copyFormatterOption (source, format, target) {\n if (options[source] === undefined) { return; }\n for (const tagDefinition of tagDefinitions) {\n if (tagDefinition.format === format) {\n set(tagDefinition, ['options', target], options[source]);\n }\n }\n }\n\n copyFormatterOption('hideLinkHrefIfSameAsText', 'anchor', 'hideLinkHrefIfSameAsText');\n copyFormatterOption('ignoreHref', 'anchor', 'ignoreHref');\n copyFormatterOption('linkHrefBaseUrl', 'anchor', 'baseUrl');\n copyFormatterOption('noAnchorUrl', 'anchor', 'noAnchorUrl');\n copyFormatterOption('noLinkBrackets', 'anchor', 'noLinkBrackets');\n\n copyFormatterOption('linkHrefBaseUrl', 'image', 'baseUrl');\n\n copyFormatterOption('unorderedListItemPrefix', 'unorderedList', 'itemPrefix');\n\n copyFormatterOption('uppercaseHeadings', 'heading', 'uppercase');\n copyFormatterOption('uppercaseHeadings', 'table', 'uppercaseHeadings');\n copyFormatterOption('uppercaseHeadings', 'dataTable', 'uppercaseHeadings');\n\n if (options['ignoreImage']) {\n for (const tagDefinition of tagDefinitions) {\n if (tagDefinition.format === 'image') {\n tagDefinition.format = 'skip';\n }\n }\n }\n\n if (options['singleNewLineParagraphs']) {\n for (const tagDefinition of tagDefinitions) {\n if (tagDefinition.format === 'paragraph' || tagDefinition.format === 'pre') {\n set(tagDefinition, ['options', 'leadingLineBreaks'], 1);\n set(tagDefinition, ['options', 'trailingLineBreaks'], 1);\n }\n }\n }\n}\n\nfunction findBase (dom, options, baseElement) {\n let result = null;\n\n const splitTag = splitSelector(baseElement);\n\n function recursiveWalk (walk, /** @type { DomNode[] } */ dom) {\n if (result) { return; }\n dom = dom.slice(0, options.limits.maxChildNodes);\n for (const elem of dom) {\n if (result) { return; }\n if (elem.name === splitTag.element) {\n const documentClasses = elem.attribs && elem.attribs.class ? elem.attribs.class.split(' ') : [];\n const documentIds = elem.attribs && elem.attribs.id ? elem.attribs.id.split(' ') : [];\n\n if (\n splitTag.classes.every(function (val) { return documentClasses.indexOf(val) >= 0; }) &&\n splitTag.ids.every(function (val) { return documentIds.indexOf(val) >= 0; })\n ) {\n result = [elem];\n return;\n }\n }\n if (elem.children) { walk(elem.children); }\n }\n }\n\n const limitedWalk = limitedDepthRecursive(\n options.limits.maxDepth,\n recursiveWalk\n );\n\n limitedWalk(dom);\n return options.returnDomByDefault ? result || dom : result;\n}\n\n/**\n * Function to walk through DOM nodes and accumulate their string representations.\n *\n * @param { RecursiveCallback } walk Recursive callback.\n * @param { DomNode[] } [dom] Nodes array to process.\n * @param { BlockTextBuilder } builder Passed around to accumulate output text.\n * @private\n */\nfunction recursiveWalk (walk, dom, builder) {\n if (!dom) { return; }\n\n const options = builder.options;\n\n const tooManyChildNodes = dom.length > options.limits.maxChildNodes;\n if (tooManyChildNodes) {\n dom = dom.slice(0, options.limits.maxChildNodes);\n dom.push({\n data: options.limits.ellipsis,\n type: 'text'\n });\n }\n\n for (const elem of dom) {\n switch (elem.type) {\n case 'text': {\n builder.addInline(he.decode(elem.data, options.decodeOptions));\n break;\n }\n case 'tag': {\n const tags = options.tags;\n const tagDefinition = tags[elem.name] || tags[''];\n const format = options.formatters[tagDefinition.format];\n format(elem, walk, builder, tagDefinition.options || {});\n break;\n }\n default:\n /* do nothing */\n break;\n }\n }\n\n return;\n}\n\n/**\n * @deprecated Import/require `{ htmlToText }` function instead!\n * @see htmlToText\n *\n * @param { string } html HTML content to convert.\n * @param { Options } [options = {}] HtmlToText options.\n * @returns { string } Plain text string.\n * @static\n */\nconst fromString = (html, options = {}) => htmlToText(html, options);\n\nmodule.exports = {\n htmlToText: htmlToText,\n fromString: fromString\n};\n\n\n//# sourceURL=webpack://dcp/./node_modules/html-to-text/lib/html-to-text.js?");
|
|
2607
|
+
eval("const { hp2Builder } = __webpack_require__(/*! @selderee/plugin-htmlparser2 */ \"./node_modules/@selderee/plugin-htmlparser2/lib/hp2-builder.cjs\");\nconst merge = __webpack_require__(/*! deepmerge */ \"./node_modules/deepmerge/dist/cjs.js\");\nconst he = __webpack_require__(/*! he */ \"./node_modules/he/he.js\");\nconst htmlparser = __webpack_require__(/*! htmlparser2 */ \"./node_modules/html-to-text/node_modules/htmlparser2/lib/index.js\");\nconst selderee = __webpack_require__(/*! selderee */ \"./node_modules/selderee/lib/selderee.cjs\");\n\nconst { BlockTextBuilder } = __webpack_require__(/*! ./block-text-builder */ \"./node_modules/html-to-text/lib/block-text-builder.js\");\nconst defaultFormatters = __webpack_require__(/*! ./formatter */ \"./node_modules/html-to-text/lib/formatter.js\");\nconst { limitedDepthRecursive, mergeDuplicatesPreferLast, get } = __webpack_require__(/*! ./helper */ \"./node_modules/html-to-text/lib/helper.js\");\n\n// eslint-disable-next-line import/no-unassigned-import\n__webpack_require__(/*! ./typedefs */ \"./node_modules/html-to-text/lib/typedefs.js\");\n\n\n/**\n * Default options.\n *\n * @constant\n * @type { Options }\n * @default\n * @private\n */\nconst DEFAULT_OPTIONS = {\n baseElements: {\n selectors: [ 'body' ],\n orderBy: 'selectors', // 'selectors' | 'occurrence'\n returnDomByDefault: true\n },\n decodeOptions: {\n isAttributeValue: false,\n strict: false\n },\n formatters: {},\n limits: {\n ellipsis: '...',\n maxBaseElements: undefined,\n maxChildNodes: undefined,\n maxDepth: undefined,\n maxInputLength: (1 << 24) // 16_777_216\n },\n longWordSplit: {\n forceWrapOnLimit: false,\n wrapCharacters: []\n },\n preserveNewlines: false,\n selectors: [\n { selector: '*', format: 'inline' },\n {\n selector: 'a',\n format: 'anchor',\n options: {\n baseUrl: null,\n hideLinkHrefIfSameAsText: false,\n ignoreHref: false,\n linkBrackets: ['[', ']'],\n noAnchorUrl: true\n }\n },\n { selector: 'article', format: 'block' },\n { selector: 'aside', format: 'block' },\n {\n selector: 'blockquote',\n format: 'blockquote',\n options: { leadingLineBreaks: 2, trailingLineBreaks: 2, trimEmptyLines: true }\n },\n { selector: 'br', format: 'lineBreak' },\n { selector: 'div', format: 'block' },\n { selector: 'footer', format: 'block' },\n { selector: 'form', format: 'block' },\n { selector: 'h1', format: 'heading', options: { leadingLineBreaks: 3, trailingLineBreaks: 2, uppercase: true } },\n { selector: 'h2', format: 'heading', options: { leadingLineBreaks: 3, trailingLineBreaks: 2, uppercase: true } },\n { selector: 'h3', format: 'heading', options: { leadingLineBreaks: 3, trailingLineBreaks: 2, uppercase: true } },\n { selector: 'h4', format: 'heading', options: { leadingLineBreaks: 2, trailingLineBreaks: 2, uppercase: true } },\n { selector: 'h5', format: 'heading', options: { leadingLineBreaks: 2, trailingLineBreaks: 2, uppercase: true } },\n { selector: 'h6', format: 'heading', options: { leadingLineBreaks: 2, trailingLineBreaks: 2, uppercase: true } },\n { selector: 'header', format: 'block' },\n {\n selector: 'hr',\n format: 'horizontalLine',\n options: { leadingLineBreaks: 2, length: undefined, trailingLineBreaks: 2 }\n },\n {\n selector: 'img',\n format: 'image',\n options: { baseUrl: null, linkBrackets: ['[', ']'] }\n },\n { selector: 'main', format: 'block' },\n { selector: 'nav', format: 'block' },\n {\n selector: 'ol',\n format: 'orderedList',\n options: { leadingLineBreaks: 2, trailingLineBreaks: 2 }\n },\n { selector: 'p', format: 'paragraph', options: { leadingLineBreaks: 2, trailingLineBreaks: 2 } },\n { selector: 'pre', format: 'pre', options: { leadingLineBreaks: 2, trailingLineBreaks: 2 } },\n { selector: 'section', format: 'block' },\n {\n selector: 'table',\n format: 'table',\n options: {\n colSpacing: 3,\n leadingLineBreaks: 2,\n maxColumnWidth: 60,\n rowSpacing: 0,\n trailingLineBreaks: 2,\n uppercaseHeaderCells: true\n }\n },\n {\n selector: 'ul',\n format: 'unorderedList',\n options: { itemPrefix: ' * ', leadingLineBreaks: 2, trailingLineBreaks: 2 }\n },\n { selector: 'wbr', format: 'wbr' },\n ],\n tables: [], // deprecated\n whitespaceCharacters: ' \\t\\r\\n\\f\\u200b',\n wordwrap: 80\n};\n\nconst concatMerge = (acc, src, options) => [...acc, ...src];\nconst overwriteMerge = (acc, src, options) => [...src];\nconst selectorsMerge = (acc, src, options) => (\n (acc.some(s => typeof s === 'object'))\n ? concatMerge(acc, src, options) // selectors\n : overwriteMerge(acc, src, options) // baseElements.selectors\n);\n\n/**\n * Preprocess options, compile selectors into a decision tree,\n * return a function intended for batch processing.\n *\n * @param { Options } [options = {}] HtmlToText options.\n * @returns { (html: string) => string } Pre-configured converter function.\n * @static\n */\nfunction compile (options = {}) {\n options = merge(\n DEFAULT_OPTIONS,\n options,\n {\n arrayMerge: overwriteMerge,\n customMerge: (key) => ((key === 'selectors') ? selectorsMerge : undefined)\n }\n );\n options.formatters = Object.assign({}, defaultFormatters, options.formatters);\n options.selectors = mergeDuplicatesPreferLast(options.selectors, (s => s.selector));\n\n handleDeprecatedOptions(options);\n\n const selectorsWithoutFormat = options.selectors.filter(s => !s.format);\n if (selectorsWithoutFormat.length) {\n throw new Error(\n 'Following selectors have no specified format: ' +\n selectorsWithoutFormat.map(s => `\\`${s.selector}\\``).join(', ')\n );\n }\n const picker = new selderee.DecisionTree(\n options.selectors.map(s => [s.selector, s])\n ).build(hp2Builder);\n\n const baseSelectorsPicker = new selderee.DecisionTree(\n options.baseElements.selectors.map((s, i) => [s, i + 1])\n ).build(hp2Builder);\n function findBaseElements (dom) {\n return findBases(dom, options, baseSelectorsPicker);\n }\n\n const limitedWalk = limitedDepthRecursive(\n options.limits.maxDepth,\n recursiveWalk,\n function (dom, builder) {\n builder.addInline(options.limits.ellipsis || '');\n }\n );\n\n return function (html) {\n return process(html, options, picker, findBaseElements, limitedWalk);\n };\n}\n\n/**\n * Convert given HTML according to preprocessed options.\n *\n * @param { string } html HTML content to convert.\n * @param { Options } options HtmlToText options (preprocessed).\n * @param { Picker<DomNode, TagDefinition> } picker\n * Tag definition picker for DOM nodes processing.\n * @param { (dom: DomNode[]) => DomNode[] } findBaseElements\n * Function to extract elements from HTML DOM\n * that will only be present in the output text.\n * @param { RecursiveCallback } walk Recursive callback.\n * @returns { string }\n */\nfunction process (html, options, picker, findBaseElements, walk) {\n const maxInputLength = options.limits.maxInputLength;\n if (maxInputLength && html && html.length > maxInputLength) {\n console.warn(\n `Input length ${html.length} is above allowed limit of ${maxInputLength}. Truncating without ellipsis.`\n );\n html = html.substring(0, maxInputLength);\n }\n\n const handler = new htmlparser.DomHandler();\n new htmlparser.Parser(handler, { decodeEntities: false }).parseComplete(html);\n\n const bases = findBaseElements(handler.dom);\n const builder = new BlockTextBuilder(options, picker);\n walk(bases, builder);\n return builder.toString();\n}\n\n/**\n * Convert given HTML content to plain text string.\n *\n * @param { string } html HTML content to convert.\n * @param { Options } [options = {}] HtmlToText options.\n * @returns { string } Plain text string.\n * @static\n *\n * @example\n * const { convert } = require('html-to-text');\n * const text = convert('<h1>Hello World</h1>', {\n * wordwrap: 130\n * });\n * console.log(text); // HELLO WORLD\n */\nfunction convert (html, options = {}) {\n return compile(options)(html);\n}\n\n/**\n * Map previously existing and now deprecated options to the new options layout.\n * This is a subject for cleanup in major releases.\n *\n * @param { Options } options HtmlToText options.\n */\nfunction handleDeprecatedOptions (options) {\n const selectorDefinitions = options.selectors;\n\n if (options.tags) {\n const tagDefinitions = Object.entries(options.tags).map(\n ([selector, definition]) => ({ ...definition, selector: selector || '*' })\n );\n selectorDefinitions.push(...tagDefinitions);\n }\n\n function set (obj, path, value) {\n const valueKey = path.pop();\n for (const key of path) {\n let nested = obj[key];\n if (!nested) {\n nested = {};\n obj[key] = nested;\n }\n obj = nested;\n }\n obj[valueKey] = value;\n }\n\n function copyFormatterOption (source, format, target) {\n if (options[source] === undefined) { return; }\n for (const definition of selectorDefinitions) {\n if (definition.format === format) {\n set(definition, ['options', target], options[source]);\n }\n }\n }\n\n copyFormatterOption('hideLinkHrefIfSameAsText', 'anchor', 'hideLinkHrefIfSameAsText');\n copyFormatterOption('ignoreHref', 'anchor', 'ignoreHref');\n copyFormatterOption('linkHrefBaseUrl', 'anchor', 'baseUrl');\n copyFormatterOption('noAnchorUrl', 'anchor', 'noAnchorUrl');\n copyFormatterOption('noLinkBrackets', 'anchor', 'noLinkBrackets');\n\n copyFormatterOption('linkHrefBaseUrl', 'image', 'baseUrl');\n\n copyFormatterOption('unorderedListItemPrefix', 'unorderedList', 'itemPrefix');\n\n copyFormatterOption('uppercaseHeadings', 'heading', 'uppercase');\n copyFormatterOption('uppercaseHeadings', 'table', 'uppercaseHeadings');\n copyFormatterOption('uppercaseHeadings', 'dataTable', 'uppercaseHeadings');\n\n if (options['ignoreImage']) {\n for (const definition of selectorDefinitions) {\n if (definition.format === 'image') {\n definition.format = 'skip';\n }\n }\n }\n\n if (options['singleNewLineParagraphs']) {\n for (const definition of selectorDefinitions) {\n if (definition.format === 'paragraph' || definition.format === 'pre') {\n set(definition, ['options', 'leadingLineBreaks'], 1);\n set(definition, ['options', 'trailingLineBreaks'], 1);\n }\n }\n }\n\n if (options['baseElement']) {\n const baseElement = options['baseElement'];\n set(\n options,\n ['baseElements', 'selectors'],\n (Array.isArray(baseElement) ? baseElement : [baseElement])\n );\n }\n if (options['returnDomByDefault'] !== undefined) {\n set(options, ['baseElements', 'returnDomByDefault'], options['returnDomByDefault']);\n }\n\n for (const definition of selectorDefinitions) {\n if (definition.format === 'anchor' && get(definition, ['options', 'noLinkBrackets'])) {\n set(definition, ['options', 'linkBrackets'], false);\n }\n }\n}\n\nfunction findBases (dom, options, baseSelectorsPicker) {\n const results = [];\n\n function recursiveWalk (walk, /** @type { DomNode[] } */ dom) {\n dom = dom.slice(0, options.limits.maxChildNodes);\n for (const elem of dom) {\n if (elem.type !== 'tag') {\n continue;\n }\n const pickedSelectorIndex = baseSelectorsPicker.pick1(elem);\n if (pickedSelectorIndex > 0) {\n results.push({ selectorIndex: pickedSelectorIndex, element: elem });\n } else if (elem.children) {\n walk(elem.children);\n }\n if (results.length >= options.limits.maxBaseElements) {\n return;\n }\n }\n }\n\n const limitedWalk = limitedDepthRecursive(\n options.limits.maxDepth,\n recursiveWalk\n );\n limitedWalk(dom);\n\n if (options.baseElements.orderBy !== 'occurrence') { // 'selectors'\n results.sort((a, b) => a.selectorIndex - b.selectorIndex);\n }\n return (options.baseElements.returnDomByDefault && results.length === 0)\n ? dom\n : results.map(x => x.element);\n}\n\n/**\n * Function to walk through DOM nodes and accumulate their string representations.\n *\n * @param { RecursiveCallback } walk Recursive callback.\n * @param { DomNode[] } [dom] Nodes array to process.\n * @param { BlockTextBuilder } builder Passed around to accumulate output text.\n * @private\n */\nfunction recursiveWalk (walk, dom, builder) {\n if (!dom) { return; }\n\n const options = builder.options;\n\n const tooManyChildNodes = dom.length > options.limits.maxChildNodes;\n if (tooManyChildNodes) {\n dom = dom.slice(0, options.limits.maxChildNodes);\n dom.push({\n data: options.limits.ellipsis,\n type: 'text'\n });\n }\n\n for (const elem of dom) {\n switch (elem.type) {\n case 'text': {\n builder.addInline(he.decode(elem.data, options.decodeOptions));\n break;\n }\n case 'tag': {\n const tagDefinition = builder.picker.pick1(elem);\n const format = options.formatters[tagDefinition.format];\n format(elem, walk, builder, tagDefinition.options || {});\n break;\n }\n default:\n /* do nothing */\n break;\n }\n }\n\n return;\n}\n\n/**\n * @deprecated Use `{ convert }` function instead!\n * @see convert\n *\n * @param { string } html HTML content to convert.\n * @param { Options } [options = {}] HtmlToText options.\n * @returns { string } Plain text string.\n * @static\n */\nconst fromString = (html, options = {}) => convert(html, options);\n\nmodule.exports = {\n compile: compile,\n convert: convert,\n fromString: fromString,\n htmlToText: convert\n};\n\n\n//# sourceURL=webpack://dcp/./node_modules/html-to-text/lib/html-to-text.js?");
|
|
2575
2608
|
|
|
2576
2609
|
/***/ }),
|
|
2577
2610
|
|
|
@@ -2611,7 +2644,7 @@ eval("\nfunction getRow (matrix, j) {\n if (!matrix[j]) { matrix[j] = []; }\n
|
|
|
2611
2644
|
\***************************************************/
|
|
2612
2645
|
/***/ (() => {
|
|
2613
2646
|
|
|
2614
|
-
eval("\n/**\n * @typedef { object } Options\n * HtmlToText options.\n *\n * @property { string | string[] } [baseElement = body]\n * The resulting text output will be composed from the text content of this element\n * (or elements if an array of strings is specified).\n *\n * Each entry is a single tag name with optional css class and id parameters,\n * e.g. `['p.class1.class2#id1#id2', 'p.class1.class2#id1#id2']`.\n *\n * @property { DecodeOptions } [decodeOptions]\n * Text decoding options given to `he.decode`.\n *\n * For more informations see the [he](https://github.com/mathiasbynens/he) module.\n *\n * @property { object.< string, FormatCallback > } [formatters = {}]\n * A dictionary with custom formatting functions for specific kinds of elements.\n *\n * Keys are custom string identifiers, values are callbacks.\n *\n * @property { LimitsOptions } [limits]\n * Options for handling complex documents and limiting the output size.\n *\n * @property { LongWordSplitOptions } [longWordSplit]\n * Describes how to wrap long words.\n *\n * @property { boolean } [preserveNewlines = false]\n * By default, any newlines `\\n` from the input HTML are collapsed into space as any other HTML whitespace characters.\n * If `true`, these newlines will be preserved in the output.\n * This is only useful when input HTML carries some plain text formatting instead of proper tags.\n *\n * @property { boolean } [returnDomByDefault = true]\n * Use the entire document if we don't find the tag defined in `Options.baseElement`.\n *\n * @property { string[] | boolean } [tables = []]\n * Allows to select and format certain tables by the `class` or `id` attribute from the HTML document.\n *\n * This is necessary because the majority of HTML E-Mails uses a table based layout.\n *\n * Prefix your table selectors with a `.` for the `class` and with a `#` for the `id` attribute.\n * All other tables are ignored (processed as layout containers, not tabular data).\n *\n * You can assign `true` to this property to format all tables.\n *\n * @property { object.< string, TagDefinition > } [tags = {}]\n * A dictionary with custom tag definitions.\n *\n * Use this to (re)define how to handle new or already supported tags.\n *\n * Empty string (`''`) as a key used for the default definition for \"any other\" tags.\n *\n * @property { string } [whitespaceCharacters = ' \\t\\r\\n\\f\\u200b']\n * All characters that are considered whitespace.\n * Default is according to HTML specifications.\n *\n * @property { number | boolean | null } [wordwrap = 80]\n * After how many chars a line break should follow in `p` elements.\n *\n * Set to `null` or `false` to disable word-wrapping.\n */\n\n/**\n * @typedef { object } DecodeOptions\n * Text decoding options given to `he.decode`.\n *\n * For more informations see the [he](https://github.com/mathiasbynens/he) module.\n *\n * @property { boolean } [isAttributeValue = false]\n * TLDR: If set to `true` - leave attribute values raw, don't parse them as text content.\n *\n * @property { boolean } [strict = false]\n * TLDR: If set to `true` - throw an error on invalid HTML input.\n */\n\n/**\n * @typedef { object } LimitsOptions\n * Options for handling complex documents and limiting the output size.\n *\n * @property { string } [ellipsis = ...]\n * A string to put in place of skipped content.\n *\n * @property { number | undefined } [maxChildNodes = undefined]\n * Process only this many child nodes of any element.\n *\n * Remaining nodes, if any, will be replaced with ellipsis.\n *\n * Text nodes are counted along with tags.\n *\n * No limit if undefined.\n *\n * @property { number | undefined } [maxDepth = undefined]\n * Only go to a certain depth starting from `Options.baseElement`.\n *\n * Replace deeper nodes with ellipsis.\n *\n * No depth limit if undefined.\n *\n * @property { number } [maxInputLength = 16_777_216]\n * If the input string is longer than this value - it will be truncated\n * and a message will be sent to `stderr`.\n *\n * Ellipsis is not used in this case.\n */\n\n/**\n * @typedef { object } LongWordSplitOptions\n * Describes how to wrap long words.\n *\n * @property { boolean } [forceWrapOnLimit = false]\n * Break long words on the `Options.wordwrap` limit when there are no characters to wrap on.\n *\n * @property { string[] } [wrapCharacters = []]\n * An array containing the characters that may be wrapped on.\n */\n\n/**\n * @typedef { object } TagDefinition\n * Describes how to handle a tag.\n *\n * @property { string } format\n * Identifier of a {@link FormatCallback}, built-in or provided in `Options.formatters` dictionary.\n *\n * @property { FormatOptions } options\n * Options to customize the formatter for this tag.\n */\n\n/**\n * @typedef { object } FormatOptions\n * Options specific to different formatters ({@link FormatCallback}).\n * This is an umbrella type definition. Each formatter supports it's own subset of options.\n *\n * @property { number } [leadingLineBreaks]\n * Number of line breaks to separate previous block from this one.\n *\n * Note that N+1 line breaks are needed to make N empty lines.\n *\n * @property { number } [trailingLineBreaks]\n * Number of line breaks to separate this block from the next one.\n *\n * Note that N+1 line breaks are needed to make N empty lines.\n *\n * @property { string | null } [baseUrl = null]\n * (Only for: `anchor` and `image` formatters.) Server host for link `href` attributes and image `src` attributes\n * relative to the root (the ones that start with `/`).\n *\n * For example, with `baseUrl = 'http://asdf.com'` and `<a href='/dir/subdir'>...</a>`\n * the link in the text will be `http://asdf.com/dir/subdir`.\n *\n * Keep in mind that `baseUrl` should not end with a `/`.\n *\n * @property { boolean } [hideLinkHrefIfSameAsText = false]\n * (Only for: `anchor` formatter.) By default links are translated in the following way:\n *\n * `<a href='link'>text</a>` => becomes => `text [link]`.\n *\n * If this option is set to `true` and `link` and `text` are the same,\n * `[link]` will be omitted and only `text` will be present.\n *\n * @property { boolean } [ignoreHref = false]\n * (Only for: `anchor` formatter.) Ignore all links. Only process internal text of anchor tags.\n *\n * @property { boolean } [noAnchorUrl = true]\n * (Only for: `anchor` formatter.) Ignore anchor links (where `href='#...'`).\n *\n * @property { boolean } [noLinkBrackets = false]\n * (Only for: `anchor` formatter.) Don't print brackets around links.\n *\n * @property { string } [itemPrefix = ' * ']\n * (Only for: `unorderedList` formatter.) String prefix for each list item.\n *\n * @property { boolean } [uppercase = true]\n * (Only for: `heading` formatter.) By default, headings (`<h1>`, `<h2>`, etc) are uppercased.\n *\n * Set this to `false` to leave headings as they are.\n *\n * @property { number | undefined } [length = undefined]\n * (Only for: `horizontalLine` formatter.) Length of the `<hr/>` line.\n *\n * If numeric value is provided - it is used.\n * Otherwise, if global `wordwrap` number is provided - it is used.\n * If neither is true, then the fallback value of 40 is used.\n *\n * @property { boolean } [trimEmptyLines = true]\n * (Only for: `blockquote` formatter.) Trim empty lines from blockquote.\n *\n * @property { boolean } [uppercaseHeaderCells = true]\n * (Only for: `table`, `dataTable` formatter.) By default, heading cells (`<th>`) are uppercased.\n *\n * Set this to `false` to leave heading cells as they are.\n *\n * @property { number } [maxColumnWidth = 60]\n * (Only for: `table`, `dataTable` formatter.) Data table cell content will be wrapped to fit this width\n * instead of global `wordwrap` limit.\n *\n * Set this to `undefined` in order to fall back to `wordwrap` limit.\n *\n * @property { number } [colSpacing = 3]\n * (Only for: `table`, `dataTable` formatter.) Number of spaces between data table columns.\n *\n * @property { number } [rowSpacing = 0]\n * (Only for: `table`, `dataTable` formatter.) Number of empty lines between data table rows.\n *\n */\n\n/**\n * @typedef { object } DomNode\n * Simplified definition of [htmlparser2](https://github.com/fb55/htmlparser2) Node type.\n *\n * Makes no distinction between elements (tags) and data nodes (good enough for now).\n *\n * @property { string } type Type of node - \"text\", \"tag\", \"comment\", \"script\", etc.\n * @property { string } [data] Content of a data node.\n * @property { string } [name] Tag name.\n * @property { object.<string,string> } [attribs] Tag attributes dictionary.\n * @property { DomNode[] } [children] Child nodes.\n * @property { DomNode } [parent] Parent node.\n */\n\n/**\n * A function to stringify a DOM node.\n *\n * @callback FormatCallback\n *\n * @param { DomNode } elem A DOM node as returned by [htmlparser2](https://github.com/fb55/htmlparser2).\n * @param { RecursiveCallback } walk Recursive callback to process child nodes.\n * @param { BlockTextBuilder } builder Passed around to accumulate output text. Contains options object.\n * @param { FormatOptions } formatOptions Options specific to this callback.\n */\n\n/**\n * A function to process child nodes.\n * Passed into a {@link FormatCallback} as an argument.\n *\n * @callback RecursiveCallback\n *\n * @param { DomNode[] } [nodes] DOM nodes array.\n * @param { BlockTextBuilder } builder Passed around to accumulate output text. Contains options object.\n */\n\n\n//# sourceURL=webpack://dcp/./node_modules/html-to-text/lib/typedefs.js?");
|
|
2647
|
+
eval("\n/**\n * @typedef { object } Options\n * HtmlToText options.\n *\n * @property { BaseElementsOptions } [baseElements]\n * Options for narrowing down to informative parts of HTML document.\n *\n * @property { DecodeOptions } [decodeOptions]\n * Text decoding options given to `he.decode`.\n *\n * For more information see the [he](https://github.com/mathiasbynens/he) module.\n *\n * @property { object.< string, FormatCallback > } [formatters = {}]\n * A dictionary with custom formatting functions for specific kinds of elements.\n *\n * Keys are custom string identifiers, values are callbacks.\n *\n * @property { LimitsOptions } [limits]\n * Options for handling complex documents and limiting the output size.\n *\n * @property { LongWordSplitOptions } [longWordSplit]\n * Describes how to wrap long words.\n *\n * @property { boolean } [preserveNewlines = false]\n * By default, any newlines `\\n` from the input HTML are collapsed into space as any other HTML whitespace characters.\n * If `true`, these newlines will be preserved in the output.\n * This is only useful when input HTML carries some plain text formatting instead of proper tags.\n *\n * @property { SelectorDefinition[] } [selectors = []]\n * Instructions for how to render HTML elements based on matched selectors.\n *\n * Use this to (re)define options for new or already supported tags.\n *\n * @property { string[] | boolean } [tables = []]\n * Deprecated. Use selectors with `format: 'dataTable'` instead.\n *\n * @property { string } [whitespaceCharacters = ' \\t\\r\\n\\f\\u200b']\n * All characters that are considered whitespace.\n * Default is according to HTML specifications.\n *\n * @property { number | boolean | null } [wordwrap = 80]\n * After how many chars a line break should follow in `p` elements.\n *\n * Set to `null` or `false` to disable word-wrapping.\n */\n\n/**\n * @typedef { object } BaseElementsOptions\n * Options for narrowing down to informative parts of HTML document.\n *\n * @property { string[] } [selectors = ['body']]\n * The resulting text output will be composed from the text content of elements\n * matched with these selectors.\n *\n * @property { 'selectors' | 'occurrence' } [orderBy = 'selectors']\n * When multiple selectors are set, this option specifies\n * whether the selectors order has to be reflected in the output text.\n *\n * `'selectors'` (default) - matches for the first selector will appear first, etc;\n *\n * `'occurrence'` - all bases will appear in the same order as in input HTML.\n *\n * @property { boolean } [returnDomByDefault = true]\n * Use the entire document if none of provided selectors matched.\n */\n\n/**\n * @typedef { object } DecodeOptions\n * Text decoding options given to `he.decode`.\n *\n * For more information see the [he](https://github.com/mathiasbynens/he) module.\n *\n * @property { boolean } [isAttributeValue = false]\n * TL;DR: If set to `true` - leave attribute values raw, don't parse them as text content.\n *\n * @property { boolean } [strict = false]\n * TL;DR: If set to `true` - throw an error on invalid HTML input.\n */\n\n/**\n * @typedef { object } LimitsOptions\n * Options for handling complex documents and limiting the output size.\n *\n * @property { string } [ellipsis = ...]\n * A string to put in place of skipped content.\n *\n * @property { number | undefined } [maxBaseElements = undefined]\n * Stop looking for new base elements after this number of matches.\n *\n * No ellipsis is used when this condition is met.\n *\n * No limit if undefined.\n *\n * @property { number | undefined } [maxChildNodes = undefined]\n * Process only this many child nodes of any element.\n *\n * Remaining nodes, if any, will be replaced with ellipsis.\n *\n * Text nodes are counted along with tags.\n *\n * No limit if undefined.\n *\n * @property { number | undefined } [maxDepth = undefined]\n * Only go to a certain depth starting from `Options.baseElement`.\n *\n * Replace deeper nodes with ellipsis.\n *\n * No depth limit if undefined.\n *\n * @property { number } [maxInputLength = 16_777_216]\n * If the input string is longer than this value - it will be truncated\n * and a message will be sent to `stderr`.\n *\n * Ellipsis is not used in this case.\n */\n\n/**\n * @typedef { object } LongWordSplitOptions\n * Describes how to wrap long words.\n *\n * @property { boolean } [forceWrapOnLimit = false]\n * Break long words on the `Options.wordwrap` limit when there are no characters to wrap on.\n *\n * @property { string[] } [wrapCharacters = []]\n * An array containing the characters that may be wrapped on.\n */\n\n/**\n * @typedef { object } SelectorDefinition\n * Describes how to handle tags matched by a selector.\n *\n * @property { string } selector\n * CSS selector. Refer to README for notes on supported selectors etc.\n *\n * @property { string } format\n * Identifier of a {@link FormatCallback}, built-in or provided in `Options.formatters` dictionary.\n *\n * @property { FormatOptions } options\n * Options to customize the formatter for this element.\n */\n\n/**\n * @typedef { object } FormatOptions\n * Options specific to different formatters ({@link FormatCallback}).\n * This is an umbrella type definition. Each formatter supports it's own subset of options.\n *\n * @property { number } [leadingLineBreaks]\n * Number of line breaks to separate previous block from this one.\n *\n * Note that N+1 line breaks are needed to make N empty lines.\n *\n * @property { number } [trailingLineBreaks]\n * Number of line breaks to separate this block from the next one.\n *\n * Note that N+1 line breaks are needed to make N empty lines.\n *\n * @property { string | null } [baseUrl = null]\n * (Only for: `anchor` and `image` formatters.) Server host for link `href` attributes and image `src` attributes\n * relative to the root (the ones that start with `/`).\n *\n * For example, with `baseUrl = 'http://asdf.com'` and `<a href='/dir/subdir'>...</a>`\n * the link in the text will be `http://asdf.com/dir/subdir`.\n *\n * Keep in mind that `baseUrl` should not end with a `/`.\n *\n * @property { boolean } [hideLinkHrefIfSameAsText = false]\n * (Only for: `anchor` formatter.) By default links are translated in the following way:\n *\n * `<a href='link'>text</a>` => becomes => `text [link]`.\n *\n * If this option is set to `true` and `link` and `text` are the same,\n * `[link]` will be omitted and only `text` will be present.\n *\n * @property { boolean } [ignoreHref = false]\n * (Only for: `anchor` formatter.) Ignore all links. Only process internal text of anchor tags.\n *\n * @property { [string, string] | false } [linkBrackets]\n * (Only for: `anchor` and `image` formatters.) Surround links with these brackets. Default: `['[', ']']`.\n *\n * Set to `false` or `['', '']` to disable.\n *\n * @property { boolean } [noAnchorUrl = true]\n * (Only for: `anchor` formatter.) Ignore anchor links (where `href='#...'`).\n *\n * @property { string } [itemPrefix = ' * ']\n * (Only for: `unorderedList` formatter.) String prefix for each list item.\n *\n * @property { boolean } [uppercase = true]\n * (Only for: `heading` formatter.) By default, headings (`<h1>`, `<h2>`, etc) are uppercased.\n *\n * Set this to `false` to leave headings as they are.\n *\n * @property { number | undefined } [length = undefined]\n * (Only for: `horizontalLine` formatter.) Length of the `<hr/>` line.\n *\n * If numeric value is provided - it is used.\n * Otherwise, if global `wordwrap` number is provided - it is used.\n * If neither is true, then the fallback value of 40 is used.\n *\n * @property { boolean } [trimEmptyLines = true]\n * (Only for: `blockquote` formatter.) Trim empty lines from blockquote.\n *\n * @property { boolean } [uppercaseHeaderCells = true]\n * (Only for: `table`, `dataTable` formatter.) By default, heading cells (`<th>`) are uppercased.\n *\n * Set this to `false` to leave heading cells as they are.\n *\n * @property { number } [maxColumnWidth = 60]\n * (Only for: `table`, `dataTable` formatter.) Data table cell content will be wrapped to fit this width\n * instead of global `wordwrap` limit.\n *\n * Set this to `undefined` in order to fall back to `wordwrap` limit.\n *\n * @property { number } [colSpacing = 3]\n * (Only for: `table`, `dataTable` formatter.) Number of spaces between data table columns.\n *\n * @property { number } [rowSpacing = 0]\n * (Only for: `table`, `dataTable` formatter.) Number of empty lines between data table rows.\n *\n */\n\n/**\n * @typedef { object } DomNode\n * Simplified definition of [htmlparser2](https://github.com/fb55/htmlparser2) Node type.\n *\n * Makes no distinction between elements (tags) and data nodes (good enough for now).\n *\n * @property { string } type Type of node - \"text\", \"tag\", \"comment\", \"script\", etc.\n * @property { string } [data] Content of a data node.\n * @property { string } [name] Tag name.\n * @property { object.<string,string> } [attribs] Tag attributes dictionary.\n * @property { DomNode[] } [children] Child nodes.\n * @property { DomNode } [parent] Parent node.\n */\n\n/**\n * A function to stringify a DOM node.\n *\n * @callback FormatCallback\n *\n * @param { DomNode } elem A DOM node as returned by [htmlparser2](https://github.com/fb55/htmlparser2).\n * @param { RecursiveCallback } walk Recursive callback to process child nodes.\n * @param { BlockTextBuilder } builder Passed around to accumulate output text. Contains options object.\n * @param { FormatOptions } formatOptions Options specific to this callback.\n */\n\n/**\n * A function to process child nodes.\n * Passed into a {@link FormatCallback} as an argument.\n *\n * @callback RecursiveCallback\n *\n * @param { DomNode[] } [nodes] DOM nodes array.\n * @param { BlockTextBuilder } builder Passed around to accumulate output text. Contains options object.\n */\n\n\n//# sourceURL=webpack://dcp/./node_modules/html-to-text/lib/typedefs.js?");
|
|
2615
2648
|
|
|
2616
2649
|
/***/ }),
|
|
2617
2650
|
|
|
@@ -2621,7 +2654,7 @@ eval("\n/**\n * @typedef { object } Options\n * HtmlToText options.\n *\n * @pro
|
|
|
2621
2654
|
\***************************************************************/
|
|
2622
2655
|
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
2623
2656
|
|
|
2624
|
-
eval("\n// eslint-disable-next-line no-unused-vars\nconst { InlineTextBuilder } = __webpack_require__(/*! ./inline-text-builder */ \"./node_modules/html-to-text/lib/inline-text-builder.js\");\n\n// eslint-disable-next-line import/no-unassigned-import\n__webpack_require__(/*! ./typedefs */ \"./node_modules/html-to-text/lib/typedefs.js\");\n\n\nfunction charactersToCodes (str) {\n return [...str]\n .map(c => '\\\\u' + c.charCodeAt(0).toString(16).padStart(4, '0'))\n .join('');\n}\n\n/**\n * Helps to handle HTML whitespaces.\n *\n * @class WhitespaceProcessor\n */\nclass WhitespaceProcessor {\n\n /**\n * Creates an instance of WhitespaceProcessor.\n *\n * @param { Options } options HtmlToText options.\n * @memberof WhitespaceProcessor\n */\n constructor (options) {\n this.whitespaceChars = (options.preserveNewlines)\n ? options.whitespaceCharacters.replace(/\\n/g, '')\n : options.whitespaceCharacters;\n const whitespaceCodes = charactersToCodes(this.whitespaceChars);\n this.leadingWhitespaceRe = new RegExp(`^[${whitespaceCodes}]`);\n this.trailingWhitespaceRe = new RegExp(`[${whitespaceCodes}]$`);\n this.allWhitespaceOrEmptyRe = new RegExp(`^[${whitespaceCodes}]*$`);\n\n if (options.preserveNewlines) {\n\n const wordOrNewlineRe = new RegExp(
|
|
2657
|
+
eval("\n// eslint-disable-next-line no-unused-vars\nconst { InlineTextBuilder } = __webpack_require__(/*! ./inline-text-builder */ \"./node_modules/html-to-text/lib/inline-text-builder.js\");\n\n// eslint-disable-next-line import/no-unassigned-import\n__webpack_require__(/*! ./typedefs */ \"./node_modules/html-to-text/lib/typedefs.js\");\n\n\nfunction charactersToCodes (str) {\n return [...str]\n .map(c => '\\\\u' + c.charCodeAt(0).toString(16).padStart(4, '0'))\n .join('');\n}\n\n/**\n * Helps to handle HTML whitespaces.\n *\n * @class WhitespaceProcessor\n */\nclass WhitespaceProcessor {\n\n /**\n * Creates an instance of WhitespaceProcessor.\n *\n * @param { Options } options HtmlToText options.\n * @memberof WhitespaceProcessor\n */\n constructor (options) {\n this.whitespaceChars = (options.preserveNewlines)\n ? options.whitespaceCharacters.replace(/\\n/g, '')\n : options.whitespaceCharacters;\n const whitespaceCodes = charactersToCodes(this.whitespaceChars);\n this.leadingWhitespaceRe = new RegExp(`^[${whitespaceCodes}]`);\n this.trailingWhitespaceRe = new RegExp(`[${whitespaceCodes}]$`);\n this.allWhitespaceOrEmptyRe = new RegExp(`^[${whitespaceCodes}]*$`);\n this.newlineOrNonWhitespaceRe = new RegExp(`(\\\\n|[^\\\\n${whitespaceCodes}])`, 'g');\n\n if (options.preserveNewlines) {\n\n const wordOrNewlineRe = new RegExp(`\\\\n|[^\\\\n${whitespaceCodes}]+`, 'gm');\n\n /**\n * Shrink whitespaces and wrap text, add to the builder.\n *\n * @param { string } text Input text.\n * @param { InlineTextBuilder } inlineTextBuilder A builder to receive processed text.\n * @param { (str: string) => string } [ transform ] A transform to be applied to words.\n */\n this.shrinkWrapAdd = function (text, inlineTextBuilder, transform = (str => str)) {\n if (!text) { return; }\n const previouslyStashedSpace = inlineTextBuilder.stashedSpace;\n let anyMatch = false;\n let m = wordOrNewlineRe.exec(text);\n if (m) {\n anyMatch = true;\n if (m[0] === '\\n') {\n inlineTextBuilder.startNewLine();\n } else if (previouslyStashedSpace || this.testLeadingWhitespace(text)) {\n inlineTextBuilder.pushWord(transform(m[0]));\n } else {\n inlineTextBuilder.concatWord(transform(m[0]));\n }\n while ((m = wordOrNewlineRe.exec(text)) !== null) {\n if (m[0] === '\\n') {\n inlineTextBuilder.startNewLine();\n } else {\n inlineTextBuilder.pushWord(transform(m[0]));\n }\n }\n }\n inlineTextBuilder.stashedSpace = (previouslyStashedSpace && !anyMatch) || (this.testTrailingWhitespace(text));\n // No need to stash a space in case last added item was a new line,\n // but that won't affect anything later anyway.\n };\n\n } else {\n\n const wordRe = new RegExp(`[^${whitespaceCodes}]+`, 'g');\n\n this.shrinkWrapAdd = function (text, inlineTextBuilder, transform = (str => str)) {\n if (!text) { return; }\n const previouslyStashedSpace = inlineTextBuilder.stashedSpace;\n let anyMatch = false;\n let m = wordRe.exec(text);\n if (m) {\n anyMatch = true;\n if (previouslyStashedSpace || this.testLeadingWhitespace(text)) {\n inlineTextBuilder.pushWord(transform(m[0]));\n } else {\n inlineTextBuilder.concatWord(transform(m[0]));\n }\n while ((m = wordRe.exec(text)) !== null) {\n inlineTextBuilder.pushWord(transform(m[0]));\n }\n }\n inlineTextBuilder.stashedSpace = (previouslyStashedSpace && !anyMatch) || this.testTrailingWhitespace(text);\n };\n\n }\n }\n\n /**\n * Test whether the given text starts with HTML whitespace character.\n *\n * @param { string } text The string to test.\n * @returns { boolean }\n */\n testLeadingWhitespace (text) {\n return this.leadingWhitespaceRe.test(text);\n }\n\n /**\n * Test whether the given text ends with HTML whitespace character.\n *\n * @param { string } text The string to test.\n * @returns { boolean }\n */\n testTrailingWhitespace (text) {\n return this.trailingWhitespaceRe.test(text);\n }\n\n /**\n * Test whether the given text contains any non-whitespace characters.\n *\n * @param { string } text The string to test.\n * @returns { boolean }\n */\n testContainsWords (text) {\n return !this.allWhitespaceOrEmptyRe.test(text);\n }\n\n /**\n * Return the number of newlines if there are no words.\n *\n * If any word is found then return zero regardless of the actual number of newlines.\n *\n * @param { string } text Input string.\n * @returns { number }\n */\n countNewlinesNoWords (text) {\n this.newlineOrNonWhitespaceRe.lastIndex = 0;\n let counter = 0;\n let match;\n while ((match = this.newlineOrNonWhitespaceRe.exec(text)) !== null) {\n if (match[0] === '\\n') {\n counter++;\n } else {\n return 0;\n }\n }\n return counter;\n }\n\n}\n\nmodule.exports = { WhitespaceProcessor: WhitespaceProcessor };\n\n\n//# sourceURL=webpack://dcp/./node_modules/html-to-text/lib/whitespace-processor.js?");
|
|
2625
2658
|
|
|
2626
2659
|
/***/ }),
|
|
2627
2660
|
|
|
@@ -3041,7 +3074,7 @@ eval("/* provided dependency */ var Buffer = __webpack_require__(/*! ./node_modu
|
|
|
3041
3074
|
/***/ ((__unused_webpack_module, exports) => {
|
|
3042
3075
|
|
|
3043
3076
|
"use strict";
|
|
3044
|
-
eval("/**\n * @file kvin.js A general-purpose library for marshaling and serializing\n * ES objects. This library is a functional superset of JSON\n * and relies on JSON for speed, adding:\n * - Typed Arrays with efficient spare representation\n * - Sparse arrays\n * - Arrays with enumerable properties\n * - Object graphs with cycles\n * - Boxed primitives (excluding Symbol)\n * - Functions (including enumerable properties, global scope)\n * - Regular Expressions\n * - undefined\n * - simple objects made with constructors\n * - objects will re-constructed with no constructor arguments\n * during deserialization\n * - enumerable properties will be copied on after construction\n * - opt-in list of supported constructors (kvin.constructorAllowList)\n *\n * This library is safe to use on user-supplied data.\n *\n * The basic implementation strategy is to marshal to an intermediate\n * format, called a 'prepared object', that can be used to recreate\n * the original object, but can also be serialized with JSON. We\n * track a list objects we have seen and their initial appearance in\n * the object graph. We rely on the /de-facto/ enumeration order of\n * properties in vanilla objects that has been present in all popular\n * browsers since the dawn of time; namely, that enumeration order is\n * object insertion order. This could cause bugs with objects with\n * cycles in less-common interpreters, such as Rhino and (especially)\n * the NJS/NGS ES3 platform by Brian Basset.\n *\n * *note* - This module free of external dependencies, and can be loaded as either a Node module,\n * a BravoJS module, or as a script tag in the browser.\n *\n * *bugs* - There are known or suspected issues in the following areas:\n * - Arrays which contain the same object more than once\n * - Arrays which mix numeric and non-numeric properties, especially if they are objects\n * - Sparse Arrays\n *\n * @author Wes Garland, wes@kingsds.network\n * @date June 2018\n *\n */\n\n\n\n{/* This prologue allows a CJS2 module's exports to be loaded with eval(readFileSync(filename)) */\n var module;\n let moduleSystemType;\n let realModule = module;\n \n if (true)\n moduleSystemType = 'webpack';\n else {}\n\n module = Object.assign({}, realModule);\n\n if (moduleSystemType === 'nodejs' || moduleSystemType === 'webpack' || moduleSystemType === 'cjs1') {\n module.declare = function kvin$$cjs1$$moduleDeclare(deps, factory) {\n factory(null, exports, null);\n module = realModule;\n return exports;\n };\n } else if (moduleSystemType === 'cjs2') {\n module = realModule;\n } else if (moduleSystemType === 'none') {\n module.declare = function kvin$$cjs1$$moduleDeclare(deps, factory) {\n let exports = {};\n factory(null, exports, null);\n module = realModule;\n\n if (typeof window === 'object')\n window.KVIN = exports;\n if (typeof globalThis === 'object')\n globalThis.KVIN = exports;\n\n return exports;\n };\n }\n/* Now initialize the module by invoking module.declare per CommonJS Modules/2.0-draft8 */\n \n/* eslint-disable indent */ module.declare([], function (require, exports, module) {\n\n/** \n * @constructor to create an alternate KVIN context. This allows us to recogonize instance of\n * the standard classes from a different JS context or have different tuning parameters. \n * @param ctors list or object of standard constructors\n */\nfunction KVIN(ctors)\n{\n // We always need to initialize the standardObjects. It is used for comparisons for primitive types etc\n this.standardObjects = {};\n for (let ctor of KVIN.prototype.ctors) {\n if (!ctor)\n continue;\n this.standardObjects[ctor.name] = ctor;\n }\n\n this.ctors = [].concat(KVIN.prototype.ctors);\n \n if (!ctors)\n return;\n\n if (Array.isArray(ctors))\n {\n for (let ctor of ctors)\n {\n if (!ctor)\n continue;\n this[ctor.name] = ctor\n for (let i=0; i < this.ctors.length; i++)\n {\n if (this.ctors[i].name === ctor.name)\n this.ctors[i] = ctor;\n }\n }\n }\n else\n {\n for (let entry of Object.entries(ctors))\n {\n for (let i=0; i < this.ctors.length; i++)\n {\n let [ name, ctor ] = entry; \n if (!ctor)\n continue;\n if (this.ctors[i].name === name)\n this.ctors[i] = ctor;\n this.standardObjects[name] = ctor;\n }\n }\n }\n}\n/*\n * Set exports.makeFunctions = true to allow deserializer to make functions.\n * If the deserializer can make functions, it is equivalent to eval() from a\n * security POV. Otherwise, the deserializer will turn functions into boxed\n * strings containing the function's source code, having a name property that\n * matches the original function.\n */\nKVIN.prototype.makeFunctions = false\n\n/* More bytes in a TypedArray than typedArrayPackThreshold will trigger\n * the code to prepare these into strings rather than arrays.\n */\nKVIN.prototype.typedArrayPackThreshold = 8\n\n/* Arrays of primitives which are >= the threshold in length are scrutinized\n * for further optimization, e.g. by run-length encoding\n */\nKVIN.prototype.scanArrayThreshold = 8\n\n\n/** Maxmimum number of arguments we can pass to a function in this engine.\n * @todo this needs to be detected at startup based on environment\n */\nconst _vm_fun_maxargs = 100000\n\nconst littleEndian = (function () {\n let ui16 = new Uint16Array(1)\n let ui8\n\n ui16[0] = 0xffef\n ui8 = new Uint8Array(ui16.buffer, ui16.byteOffset, ui16.byteLength)\n\n if (ui8[0] === 0x0ff) {\n console.error('KVIN: Detected big-endian platform')\n return false\n }\n\n return true\n})()\n\n/** Pre-defined constructors, used to compact payload */\nKVIN.prototype.ctors = [\n Object,\n Int8Array,\n Uint8Array,\n Uint8ClampedArray,\n Int16Array,\n Uint16Array,\n Int32Array,\n Uint32Array,\n Float32Array,\n Float64Array,\n RegExp,\n Number,\n String,\n Boolean,\n Array,\n Function,\n Error,\n Promise,\n typeof URL !== 'undefined' ? URL : undefined, /* not part of ES => feature-test */\n Date,\n];\n\nKVIN.prototype.userCtors = {}; /**< name: implementation for user-defined constructors that are not props of global */\n\n/** Take a 'prepared object' (which can be represented by JSON) and turn it\n * into an object which resembles the object it was created from.\n *\n * @param seen An array objects we have already seen in this\n * object graph; used to track cycles.\n * @param po A prepared object representing a value or a primitive\n * @param position A string respresenting our position within\n * the graph. Used only for error messages.\n * @returns the value encoded by po\n */\nKVIN.prototype.unprepare = function unprepare (seen, po, position) {\n switch (typeof po) {\n case 'boolean':\n case 'number':\n case 'string':\n return po;\n }\n if (po.hasOwnProperty('ctr')) {\n switch (typeof po.ctr) {\n case 'string':\n if (!po.ctr.match(/^[A-Za-z_0-9$][A-Za-z_0-9$]*$/)) {\n if (this.constructorAllowlist && this.constructorAllowlist.indexOf(po.ctr) === -1) {\n throw new Error('Allowlist does not include constructor ' + po.ctr)\n }\n throw new Error('Invalid constructor name: ' + po.ctr)\n }\n break\n case 'number':\n if (!(po.ctr >= 0 && po.ctr < this.ctors.length)) {\n throw new Error('Invalid constructor number: ' + po.ctr)\n }\n break\n default:\n throw new Error('Invalid constructor label type ' + typeof po.ctr)\n }\n }\n if (po.hasOwnProperty('raw')) {\n if (typeof po.raw === 'object' && po.raw !== null && !Array.isArray(po.raw))\n {\n if (!po.used)\n po.used = true;\n else\n return JSON.parse(JSON.stringify(po.raw));\n return Object.assign(new this.standardObjects.Object(), po.raw);\n }\n return po.raw;\n }\n if (po.hasOwnProperty('ptv')) {\n return po.ptv; /* deprecated: only created by v3-6 */\n }\n if (po.hasOwnProperty('number')) {\n return unprepare$number(po.number);\n }\n if (po.hasOwnProperty('bigint')) {\n return unprepare$bigint(po.bigint);\n }\n if (po.hasOwnProperty('fnName')) {\n return this.unprepare$function(seen, po, position)\n }\n if (po.hasOwnProperty('mapKeys') || po.hasOwnProperty('mapVals')) {\n return this.unprepare$Map(seen, po, position)\n }\n if (po.hasOwnProperty('ab16') || po.hasOwnProperty('isl16')) {\n return this.unprepare$ArrayBuffer16(seen, po, position)\n }\n if (po.hasOwnProperty('ab8') || po.hasOwnProperty('isl8')) {\n return this.unprepare$ArrayBuffer8(seen, po, position)\n }\n if (po.hasOwnProperty('arr')) {\n return this.unprepare$Array(seen, po, position)\n }\n if (po.hasOwnProperty('ctr')) {\n return this.unprepare$object(seen, po, position)\n }\n if (po.hasOwnProperty('json')) {\n return JSON.parse(po.json)\n }\n if (po.hasOwnProperty('undefined')) {\n return undefined\n }\n\n if (Object.hasOwnProperty.call(po, 'resolve')) {\n // Unprepare a Promise by assuming po.resolve is a marshalled value.\n const promise = Promise.resolve(this.unmarshal(po.resolve));\n seen.push(promise);\n return promise;\n }\n\n if (po.hasOwnProperty('seen')) {\n if (!seen.hasOwnProperty(po.seen)) {\n throw new Error('Seen-list corruption detected at index ' + po.seen)\n }\n return seen[po.seen]\n }\n throw new TypeError('Invalid preparation formula at ' + position)\n}\n\nKVIN.prototype.unprepare$object = function unprepare$object (seen, po, position) {\n let o\n let constructor;\n\n function construct(constructor, args) {\n function fun() {\n return constructor.apply(this, args);\n }\n fun.prototype = constructor.prototype;\n return new fun();\n }\n \n if (typeof po.ctr === 'string' && !po.ctr.match(/^[1-9][0-9]*$/)) {\n if (this.userCtors.hasOwnProperty(po.ctr))\n constructor = this.userCtors[po.ctr];\n else \n constructor = eval(po.ctr) /* pre-validated! */ // eslint-disable-line\n } else {\n constructor = this.ctors[po.ctr]\n }\n\n if (po.hasOwnProperty('args'))\n o = construct(constructor, po.args);\n else if (po.hasOwnProperty('arg'))\n o = new constructor(po.arg);\n else\n o = new constructor(); // eslint-disable-line\n\n if (po.ctr === 'Error')\n {\n delete o.stack;\n delete o.lineNumber;\n delete o.fileName;\n }\n \n seen.push(o)\n\n if (po.hasOwnProperty('ps')) {\n for (let prop in po.ps) {\n if (po.ps.hasOwnProperty(prop)) {\n o[prop] = this.unprepare(seen, po.ps[prop], position + '.' + prop)\n }\n }\n }\n\n return o\n}\n\nKVIN.prototype.unprepare$function = function unprepare$function (seen, po, position) {\n let obj, fn\n let fnName = po.fnName\n\n /* A function is basically a callable object */\n po.ctr = this.ctors.indexOf(Object)\n delete po.fnName\n obj = this.unprepare(seen, po, position)\n\n if (!this.makeFunctions) {\n obj.name = fnName\n return obj\n }\n\n fn = (new Function('return ' + po.arg))() // eslint-disable-line\n if (po.hasOwnProperty('ps')) {\n for (let prop in po.ps) {\n fn[prop] = obj[prop]\n }\n }\n\n return fn\n}\n\nKVIN.prototype.unprepare$Map = function unprepare$Map (seen, po, position) {\n \n let m = new Map();\n\n seen.push(m)\n\n let mapKeyArr = this.unprepare$Array(seen, po.mapKeys, position);\n let mapValArr = this.unprepare$Array(seen, po.mapVals, position);\n\n for (let i = 0; i < mapKeyArr.length; i++)\n {\n m.set(mapKeyArr[i], mapValArr[i]);\n }\n\n if (po.hasOwnProperty('ps')) {\n for (let prop in po.ps) {\n if (po.ps.hasOwnProperty(prop)) {\n m[prop] = this.unprepare(seen, po.ps[prop], position + '.' + prop)\n }\n }\n }\n\n return m;\n}\n\nfunction unprepare$bigint(arg) {\n return BigInt(arg);\n}\n\nfunction unprepare$number(arg) {\n return parseFloat(arg);\n}\n \n/**\n * arr:[] - Array of primitives of prepared objects\n * lst:N - repeat last element N times\n * ps:[] - property list\n */\nKVIN.prototype.unprepare$Array = function unprepare$Array (seen, po, position) {\n let a = []\n let last\n\n seen.push(a)\n\n for (let i = 0; i < po.arr.length; i++) {\n if (typeof po.arr[i] === 'object') {\n if (po.arr[i].lst) {\n for (let j = 0; j < po.arr[i].lst; j++) {\n a.push(this.unprepare(seen, last, position + '.' + (i + j)))\n }\n continue\n }\n a.push(this.unprepare(seen, po.arr[i], position + '.' + i))\n last = po.arr[i]\n } else {\n a.push(po.arr[i])\n last = prepare$primitive(a[a.length-1], 'unprepare$Array')\n }\n }\n\n if (po.hasOwnProperty('isl')) {\n for (let prop in po.isl) {\n let island = po.isl[prop]\n let els = Array.isArray(island.arr) ? island.arr : this.unprepare$Array(seen, island.arr, [ position, 'isl', prop ].join('.'))\n\n if (els.length - 3 <= _vm_fun_maxargs) {\n if (els.length && (a.length < island['@'] + els.length)) {\n a.length = island['@'] + els.length\n }\n a.splice.apply(a, [island['@'], els.length].concat(els))\n } else {\n for (let i=0; i < els.length; i++) {\n a[i + +island['@']] = els[i]\n }\n }\n }\n }\n\n if (po.hasOwnProperty('ps')) {\n for (let prop in po.ps) {\n if (typeof po.ps[prop] === 'object') {\n a[prop] = this.unprepare(seen, po.ps[prop], position + '.' + prop)\n } else {\n a[prop] = po.ps[prop]\n }\n }\n }\n\n if (po.len) {\n a.length = po.len\n }\n\n return a\n}\n\n/** The ab8 (array buffer 8 bit) encoding encodes TypedArrays and related types by\n * converting them to Latin-1 strings full of binary data in 8-bit words.\n *\n * The isl8 (islands) encoding is almost the same, except that it encodes only\n * sequences of mostly-non-zero sections of the string.\n */\nKVIN.prototype.unprepare$ArrayBuffer8 = function unprepare$ArrayBuffer8 (seen, po, position) {\n let i8\n let bytes\n let constructor;\n\n if (typeof po.ctr === 'string' && !po.ctr.match(/^[1-9][0-9]*$/)) {\n constructor = eval(po.ctr) /* pre-validated! */ // eslint-disable-line\n } else {\n constructor = this.ctors[po.ctr]\n }\n\n if (po.hasOwnProperty('ab8')) {\n bytes = po.ab8.length\n } else {\n bytes = po.len\n }\n i8 = new Int8Array(bytes)\n if (po.hasOwnProperty('ab8')) {\n for (let i = 0; i < po.ab8.length; i++) {\n i8[i] = po.ab8.charCodeAt(i)\n }\n } else {\n for (let j = 0; j < po.isl8.length; j++) {\n for (let i = 0; i < po.isl8[j][0].length; i++) {\n i8[po.isl8[j]['@'] + i] = po.isl8[j][0].charCodeAt(i)\n }\n }\n }\n let o = new constructor(i8.buffer, i8.byteOffset) // eslint-disable-line;\n seen.push(o)\n return o;\n}\n\n/** The ab16 (array buffer 16 bit) encoding encodes TypedArrays and related types by\n * converting them to strings full of binary data in 16-bit words. Buffers\n * with an odd number of bytes encode an extra byte 'eb' at the end by itself.\n *\n * The isl16 (islands) encoding is almost the same, except that it encodes only\n * sequences of mostly-non-zero sections of the string.\n */\n KVIN.prototype.unprepare$ArrayBuffer16 = function unprepare$ArrayBuffer16 (seen, po, position) {\n let i16, i8, words\n let bytes\n let constructor;\n\n if (typeof po.ctr === 'string' && !po.ctr.match(/^[1-9][0-9]*$/)) {\n constructor = eval(po.ctr) /* pre-validated! */ // eslint-disable-line\n } else {\n constructor = this.ctors[po.ctr]\n }\n\n if (po.hasOwnProperty('ab16')) {\n bytes = po.ab16.length * 2\n if (po.hasOwnProperty('eb')) {\n bytes++\n }\n } else {\n bytes = po.len\n }\n\n words = Math.floor(bytes / 2) + (bytes % 2)\n i16 = new Int16Array(words)\n if (po.hasOwnProperty('ab16')) {\n for (let i = 0; i < po.ab16.length; i++) {\n i16[i] = po.ab16.charCodeAt(i)\n }\n } else {\n for (let j = 0; j < po.isl16.length; j++) {\n for (let i = 0; i < po.isl16[j][0].length; i++) {\n i16[po.isl16[j]['@'] + i] = po.isl16[j][0].charCodeAt(i)\n }\n }\n }\n i8 = new Int8Array(i16.buffer, i16.byteOffset, bytes)\n if (po.hasOwnProperty('eb')) {\n i8[i8.byteLength - 1] = po.eb.charCodeAt(0)\n }\n\n if (!littleEndian) {\n for (let i = 0; i < i8.length; i += 2) {\n i8[(i * 2) + 0] = i8[(i * 2) + 0] ^ i8[(i * 2) + 1]\n i8[(i * 2) + 1] = i8[(i * 2) + 1] ^ i8[(i * 2) + 0]\n i8[(i * 2) + 0] = i8[(i * 2) + 0] ^ i8[(i * 2) + 1]\n }\n }\n let o = new constructor(i8.buffer, i8.byteOffset) // eslint-disable-line\n seen.push(o)\n return o\n}\n\n/* Primitives and primitive-like objects do not have any special\n * marshaling requirements -- specifically, we don't need to\n * iterate over their properties in order to serialize them; we\n * can let JSON.stringify() do any heavy lifting.\n */\nKVIN.prototype.isPrimitiveLike = function isPrimitiveLike (o, seen) {\n if (o === null || typeof o === 'string' || typeof o === 'boolean')\n return true;\n\n if (typeof o === 'number')\n return Number.isFinite(o);\n\n if (typeof o !== 'object')\n return false;\n \n if (o.constructor === this.standardObjects.Object && Object.keys(o).length === 0)\n return true;\n\n if (o.constructor === this.standardObjects.Array && o.length === 0 && Object.keys(o).length === 0)\n return true;\n\n if (o.constructor !== this.standardObjects.Object && o.constructor !== this.standardObjects.Array)\n return false;\n\n if (Array.isArray(o)) {\n if (Object.keys(o).length !== o.length) {\n return false /* sparse array or named props */\n }\n }\n\n seen = seen.concat(o);\n for (let prop in o) {\n if (!o.hasOwnProperty(prop))\n return false;\n if (seen.indexOf(o[prop]) !== -1)\n return false; \n if (!this.isPrimitiveLike(o[prop], seen))\n return false;\n }\n\n return true\n}\n\n/**\n * Serialize an instance of Error, preserving standard-ish non-enumerable properties \n */ \nfunction prepare$Error(o)\n{\n let ret = {\n ctr: 'Error',\n ps: {},\n arg: o.message\n };\n\n for (let prop of ['code', 'stack', 'lineNumber', 'fileName'])\n if (o.hasOwnProperty(prop))\n ret.ps[prop] = o[prop];\n for (let prop in o)\n if (o.hasOwnProperty(prop))\n ret.ps[prop] = o[prop];\n\n return ret;\n}\n \n/** Take an arbitrary object and turn it into a 'prepared object'.\n * A prepared object can always be represented with JSON.\n *\n * @param seen An array objects we have already seen in this\n * object graph; used to track cycles.\n * @param o The object that the prepared object reflects\n * @returns A prepared object\n */\nKVIN.prototype.prepare = function prepare (seen, o, where) {\n let i, ret\n let po = {}\n\n if (typeof o === 'number') {\n return prepare$number(o)\n }\n if (typeof o === 'bigint') {\n return prepare$bigint(o)\n }\n if (this.isPrimitiveLike(o, seen)) {\n if (!Array.isArray(o) || o.length < this.scanArrayThreshold)\n return prepare$primitive(o, where)\n }\n if (typeof o === 'undefined') {\n return prepare$undefined(o)\n }\n /* value types below here can be used as targets of cycles */\n if ((i = seen.indexOf(o)) === -1) {\n seen.push(o)\n } else {\n return { seen: i }\n }\n if (Array.isArray(o)) {\n return this.prepare$Array(seen, o, where)\n }\n if (ArrayBuffer.isView(o)) {\n return this.prepare$ArrayBuffer(o)\n }\n if (o.constructor === Map) {\n return this.prepare$Map(seen, o, where)\n }\n if (o.constructor === WeakMap) {\n return prepare$WeakMap(o)\n }\n if (o.constructor === String || o.constructor === Number || o.constructor === Boolean) {\n return this.prepare$boxedPrimitive(o)\n }\n if (o.constructor === RegExp) {\n return this.prepare$RegExp(o)\n }\n\n if (o instanceof Promise || o instanceof this.standardObjects.Promise) {\n /**\n * Let the caller replace the `resolve` property with its marshalled\n * resolved value.\n */\n return { resolve: o };\n }\n\n if (o instanceof Error || o instanceof this.standardObjects.Error) {\n /* special-case Error to get non-enumerable properties */\n return prepare$Error(o);\n }\n\n if (typeof o.constructor === 'undefined') {\n console.warn('KVIN Warning: ' + where + ' is missing .constructor -- skipping')\n return prepare$undefined(o)\n }\n\n ret = { ctr: this.ctors.indexOf(o.constructor), ps: po }\n if (ret.ctr === -1) {\n /**\n * If the constructor is `Object` from another context, the indexOf check\n * would fail. So if the name of `o`'s constructor matches one of the valid\n * constructors, use the index from the mapped array to get the proper\n * constructor index.\n */\n const constructorNames = this.ctors.map((ctor) => ctor.name);\n const ctrIndex = constructorNames.indexOf(o.constructor.name);\n if (ctrIndex !== -1) {\n ret.ctr = ctrIndex;\n /**\n * Fix the `o`'s constructor to match its constructor in the current\n * context so that later equality/instanceof checks don't fail.\n */\n o.constructor = this.ctors[ctrIndex];\n } else {\n ret.ctr = o.constructor.name || this.ctors.indexOf(Object)\n }\n }\n\n if (typeof o === 'function') {\n ret.fnName = o.name\n }\n\n if (typeof o.toKVIN === 'function')\n return Object.assign(ret, o.toKVIN(o, this));\n else if (typeof o.toJSON === 'function')\n ret.arg = o.toJSON();\n else if (o.toString !== this.standardObjects.Object.prototype.toString)\n ret.arg = o.toString();\n\n if (typeof o.hasOwnProperty === 'undefined') {\n return ret;\n }\n\n /* Iterate over the properties and prepare each in turn, recursing\n * with a depth-first traversal of the object graph. Iteration order\n * must match unprepare()!\n */\n for (let prop in o) {\n if (!o.hasOwnProperty(prop)) {\n continue\n }\n\n switch (typeof o[prop]) {\n case 'function':\n case 'object':\n if (o[prop] !== null) {\n if (typeof o[prop].constructor !== 'undefined'\n && o[prop].constructor !== this.standardObjects.Object && o[prop].constructor.constructor !== this.standardObjects.Object\n && o[prop].constructor !== this.standardObjects.Function && o[prop].constructor.constructor !== this.standardObjects.Function\n && o[prop].constructor !== this.standardObjects.Function && o[prop].constructor.constructor.name !== \"Function\" /* vm context issue /wg aug 2020 */\n ) {\n throw new Error(`Cannot serialize property ${where}.${prop} - multiple inheritance is not supported.`);\n }\n if ((i = seen.indexOf(o[prop])) === -1) {\n po[prop] = this.prepare(seen, o[prop], where + '.' + prop)\n } else {\n po[prop] = { seen: i }\n }\n break\n } /* else fallthrough */\n case 'number':\n po[prop] = prepare$number(o[prop]);\n break;\n case 'bigint':\n po[prop] = prepare$bigint(o[prop]);\n break;\n case 'boolean':\n case 'string':\n po[prop] = prepare$primitive(o[prop], where + '.' + prop)\n break\n case 'undefined':\n po[prop] = prepare$undefined(o[prop])\n break\n default:\n throw new TypeError('Cannot serialize property ' + prop + ' which is a ' + typeof o[prop])\n }\n }\n\n return ret\n}\n\n/** Prepare an Array. Sparse arrays and arrays with properties\n * are supported, and represented reasonably efficiently, as are\n * arrays of repeated values.\n *\n * @param seen The current seen list for this marshal - things pointers point to\n * @param o The array we are preparing\n * @param where Human description of where we are in the object, for debugging purposes\n */\n KVIN.prototype.prepare$Array = function prepare$Array (seen, o, where) {\n let pa = { arr: [] }\n let keys = Object.keys(o)\n let lastJson = NaN\n let json\n let lstTotal = 0\n\n for (let i = 0; i < o.length; i++) {\n if (!o.hasOwnProperty(i)) {\n break /* sparse array */\n }\n if (typeof o[i] !== 'object' && this.isPrimitiveLike(o[i], seen)) {\n pa.arr.push(o[i])\n } else {\n pa.arr.push(this.prepare(seen, o[i], where + '.' + i))\n }\n\n json = JSON.stringify(pa.arr[pa.arr.length - 1])\n if (json === lastJson) {\n if (pa.arr[pa.arr.length - 2].lst) {\n pa.arr[pa.arr.length - 2].lst++\n pa.arr.length--\n lstTotal++\n } else {\n pa.arr[pa.arr.length - 1] = {lst: 1}\n }\n } else {\n lastJson = json\n }\n }\n\n if (keys.length !== o.length) {\n /* sparse array or array with own properties - difference between sparse entry and value=undefined preserved */\n for (let j = 0; j < keys.length; j++) {\n let key = keys[j]\n let idx = +key\n if (idx < j && pa.arr.hasOwnProperty(idx)) { /* test order for speed */\n continue\n }\n if (typeof idx === 'number' && o.hasOwnProperty(idx + 1)) {\n let island = { '@':idx, arr:[] }\n /* island of data inside sparse array */\n if (!pa.isl) {\n pa.isl = []\n }\n for (let k = idx; o.hasOwnProperty(k); k++) {\n island.arr.push(o[k])\n }\n j += island.arr.length - 1\n if (island.arr.length >= this.scanArrayThreshold) {\n let tmp = this.prepare(seen, island.arr, where + '.' + 'isl@' + (j - island.arr.length))\n if (tmp.hasOwnProperty('arr')) {\n island.arr = tmp\n } else {\n pa.isl.push(island)\n }\n }\n pa.isl.push(island)\n continue\n }\n if (!pa.hasOwnProperty('ps')) {\n pa.ps = {}\n }\n if (typeof o[key] !== 'object' && this.isPrimitiveLike(o[key], seen)) {\n pa.ps[key] = o[key]\n } else {\n pa.ps[key] = this.prepare(seen, o[key], where + '.' + key)\n }\n }\n }\n\n if (pa.arr.length + lstTotal !== o.length) {\n pa.len = o.length\n }\n return pa\n}\n\n/** Prepare a Map. This can be robustly handled with the existing array preparation, as\n * long as we differentiate it from normal arrays and treat keys and values separately.\n *\n * @param seen The current seen list for this marshal - things pointers point to\n * @param o The Map we are preparing\n * @param where Human description of where we are in the object, for debugging purposes\n */\n KVIN.prototype.prepare$Map = function prepare$Map (seen, o, where) {\n\n let pm = { mapKeys: [], mapVals: [] }\n\n let mapKeyArr = Array.from(o.keys());\n pm.mapKeys = this.prepare$Array(seen, mapKeyArr, where);\n\n let mapValArr = Array.from(o.values());\n pm.mapVals = this.prepare$Array(seen, mapValArr, where);\n\n let keys = Object.keys(o)\n if (keys.length !== o.length) {\n for (let j = 0; j < keys.length; j++) {\n let key = keys[j]\n if (!pm.hasOwnProperty('ps')) {\n pm.ps = {}\n }\n if (typeof o[key] !== 'object' && this.isPrimitiveLike(o[key])) {\n pm.ps[key] = o[key]\n } else {\n pm.ps[key] = this.prepare(seen, o[key], where + '.' + key)\n }\n }\n }\n\n return pm;\n}\n\n/** Prepare a WeakMap. The closest to correct behaviour for a WeakMap serialized over\n * a network is for the resulting WeakMap to be empty, since the WeakMap keys are\n * by design never directly referenced or retained, and cannot be iterated on.\n * This is why we pass an empty array to the constructor.\n *\n * @param o The WeakMap we are preparing\n */\n KVIN.prototype.prepare$WeakMap = function prepare$WeakMap (o) {\n return { ctr: ctors.indexOf(o.constructor), arg: [] }\n}\n\n/** Detect JavaScript strings which contain ill-formed UTF-16 sequences */\nfunction notUnicode(s) {\n if (/[\\ud800-\\udbff][^\\udc00-\\udfff]/.test(s)) {\n return true /* high-surrogate without low-surrogate */\n }\n\n if (/[^\\ud800-\\udbff][\\udc00-\\udfff]/.test(s)) {\n return true /* low-surrogate without high-surrogate */\n }\n\n return false\n}\n/** Prepare an ArrayBuffer into UCS-2, returning null when we cannot guarantee\n * that the UCS-2 is also composed of valid UTF-16 code points\n *\n * @see unprepare$ArrayBuffer16\n */\nKVIN.prototype.prepare$ArrayBuffer16 = function prepare$ArrayBuffer16 (o) {\n let ret = { ctr: this.ctors.indexOf(o.constructor) }\n let nWords = Math.floor(o.byteLength / 2)\n let s = ''\n\n if (ret.ctr === -1)\n ret.ctr = o.constructor.name\n\n if (littleEndian) {\n let ui16 = new Uint16Array(o.buffer, o.byteOffset, nWords)\n for (let i = 0; i < nWords; i++) {\n s += String.fromCharCode(ui16[i])\n }\n } else {\n let ui8 = new Uint8Array(o.buffer, o.byteOffset, o.byteLength)\n for (let i = 0; i < nWords; i++) {\n s += String.fromCharCode((ui8[0 + (2 * i)] << 8) + (ui8[1 + (2 * i)]))\n }\n }\n\n let manyZeroes = '\\u0000\\u0000\\u0000\\u0000'\n if (s.indexOf(manyZeroes) === -1) {\n ret.ab16 = s\n } else {\n /* String looks zero-busy: represent via islands of mostly non-zero (sparse string). */\n // let re = /([^\\u0000]+/g\n let re = /([^\\u0000]+(.{0,3}([^\\u0000]|$))*)+/g\n let island\n\n ret.isl16 = []\n ret.len = o.byteLength\n while ((island = re.exec(s))) {\n ret.isl16.push({0: island[0].replace(/\\u0000*$/, ''), '@': island.index})\n }\n }\n if ((2 * nWords) !== o.byteLength) {\n let ui8 = new Uint8Array(o.buffer, o.byteOffset + o.byteLength - 1, 1)\n ret.eb = ui8[0]\n }\n\n if (ret.ab16 && notUnicode(ret.ab16)) {\n return null\n } else if (ret.isl16) {\n for (let i = 0; i < ret.isl16.length; i++) {\n if (notUnicode(ret.isl16[i])) {\n return null\n }\n }\n }\n return ret\n}\n\n/** Encode an ArrayBuffer (TypedArray) into a string composed solely of Latin-1 characters.\n * Strings with many zeroes will be represented as sparse-string objects.\n */\nKVIN.prototype.prepare$ArrayBuffer8 = function prepare$ArrayBuffer8 (o) {\n let ret = { ctr: this.ctors.indexOf(o.constructor) }\n\n if (ret.ctr === -1)\n ret.ctr = o.constructor.name\n\n const mss = _vm_fun_maxargs - 1\n let ui8 = new Uint8Array(o.buffer, o.byteOffset, o.byteLength)\n let segments = []\n let s\n\n for (let i=0; i < ui8.length / mss; i++) {\n segments.push(String.fromCharCode.apply(null, ui8.slice(i * mss, (i + 1) * mss)))\n }\n s = segments.join('')\n\n let manyZeroes = '\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000'\n if (s.indexOf(manyZeroes) === -1) {\n ret.ab8 = s\n } else {\n /* String looks zero-busy: represent via islands of mostly non-zero (sparse string). */\n // let re = /([^\\u0000]+/g\n let re = /([^\\u0000]+(.{0,3}([^\\u0000]|$))*)+/g\n let island\n\n ret.isl8 = []\n ret.len = o.byteLength\n while ((island = re.exec(s))) {\n ret.isl8.push({0: island[0].replace(/\\u0000*$/, ''), '@': island.index})\n }\n }\n\n return ret\n}\n\n/** Encode an ArrayBuffer (TypedArray) into a string, trying multiple methods to determine\n * optimimum size/performance. The this.tune variable affects the behaviour of this code this:\n *\n * \"speed\" - only do naive encoding: floats get represented as byte-per-digit strings\n * \"size\" - try the naive, ab8, and ab16 encodings; pick the smallest\n * neither - try the naive encoding if under typedArrayPackThreshold and use if smaller than\n * ab8; otherwise, use ab8\n */\nKVIN.prototype.prepare$ArrayBuffer = function prepare$ArrayBuffer (o) {\n let naive, naiveJSONLen;\n let ab8, ab8JSONLen;\n let ab16, ab16JSONLen;\n\n if (this.tune === \"speed\" || this.tune === \"size\" || (o.byteLength < this.typedArrayPackThreshold)) {\n naive = { ctr: this.ctors.indexOf(o.constructor), arg: Array.prototype.slice.call(o) }\n if (this.tune === \"speed\") {\n return naive\n }\n }\n\n naiveJSONLen = naive ? JSON.stringify(naive).length : Infinity\n\n ab8 = this.prepare$ArrayBuffer8(o)\n if (this.tune !== \"size\") {\n if (naive && naive.length < ab8.length) {\n return naive\n }\n return ab8\n }\n\n ab16 = this.prepare$ArrayBuffer16(o)\n ab8JSONLen = JSON.stringify(ab8).length;\n ab16JSONLen = ab16 ? JSON.stringify(ab16).length : Infinity\n\n if (ab16JSONLen < ab8JSONLen && ab16JSONLen < naiveJSONLen) {\n return ab16\n }\n if (naiveJSONLen < ab8JSONLen) {\n return naive\n }\n\n return ab8;\n}\n\nKVIN.prototype.prepare$RegExp = function prepare$RegExp (o) {\n return { ctr: this.ctors.indexOf(o.constructor), arg: o.toString().slice(1, -1) }\n}\n\nKVIN.prototype.prepare$boxedPrimitive = function prepare$boxedPrimitive (o) {\n return { ctr: this.ctors.indexOf(o.constructor), arg: o.toString() }\n}\n\nfunction prepare$bigint (n) {\n return { bigint: n.toString() }\n}\n\nfunction prepare$number (n) {\n if (!Number.isFinite(n))\n return { number: n + '' };\n\n if (1/n === -Infinity)\n return { json: \"-0\" };\n\n return n;\n}\n \n/* Store primitives and sort-of-primitives (like object literals) directly */\nfunction prepare$primitive (primitive, where) {\n switch (typeof po) {\n case 'boolean':\n case 'number': /* not all cases, see prepare$number */\n case 'string':\n return primitive;\n }\n return { raw: primitive };\n}\n\nfunction prepare$undefined (o) {\n return { undefined: true }\n}\n\n/** Prepare a value for serialization\n * @param what any (supported) js value\n * @returns an object which can be serialized with json\n */\nKVIN.prototype.marshal = function serialize$$marshal (what) {\n return {_serializeVerId: this.serializeVerId, what: this.prepare([], what, 'top')}\n}\n\n/**\n * Prepare a value that is a Promise or contains a Promise for serialization.\n *\n * Removes cycles in objects to enable stringification using `JSON.stringify`.\n *\n * @param {*} value A supported js value that can be marshalled\n * @returns {Promise<object>} An object which can be serialized with\n * `JSON.stringify`\n */\nKVIN.prototype.marshalAsync = async function serialize$$marshalAsync(value, isRecursing = false) {\n /**\n * First, have marshal memoize returned an object graph with any instances of\n * Promise found during the marshal operation with { resolve: X }, where X is\n * an instance of Promise.\n *\n * If we're recursing, we're traversing a marshaled object and shouldn't\n * redundantly marshal a nested part of it.\n */\n let marshalledObject;\n if (!isRecursing) {\n marshalledObject = this.marshal(value);\n } else {\n marshalledObject = value;\n }\n\n /**\n * Then, traverse the marshalled object, looking for these Promise memos\n * (resolve property). await the promise (X above) and replace it in the\n * marshaled object with the marshaled representation of the resolve value.\n */\n for (const key in marshalledObject) {\n if (!Object.hasOwnProperty.call(marshalledObject, key)) {\n continue;\n }\n\n switch (typeof marshalledObject[key]) {\n case 'object':\n if (marshalledObject[key] === null) {\n continue;\n }\n\n if (\n typeof marshalledObject[key].resolve !== 'undefined' &&\n marshalledObject[key].resolve instanceof Promise\n ) {\n marshalledObject[key].resolve = await this.marshalAsync(\n await marshalledObject[key].resolve,\n );\n }\n\n /**\n * Recursively traverse the marshalled object\n *\n * Operating on the marshalled object graph means we know for certain we\n * are working on a directed acyclic graph (DAG); prepares's \"seen\"\n * array argument expresses cycles separately.\n */\n marshalledObject[key] = await this.marshalAsync(\n marshalledObject[key],\n true,\n );\n break;\n default:\n break;\n }\n }\n\n return marshalledObject;\n}\n\n/** Turn a marshaled (prepared) value back into its original form\n * @param obj a prepared object - the output of this.marshal()\n * @returns object an object resembling the object originally passed to this.marshal()\n */\nKVIN.prototype.unmarshal = function serialize$$unmarshal (obj) {\n if (typeof obj !== 'object' || obj === null) {\n throw new Error(`Cannot unmarshal type ${typeof obj} or null.`)\n }\n if (!obj.hasOwnProperty('_serializeVerId')) {\n try {\n let str = JSON.stringify(obj)\n throw new Error('Invalid serialization format (' + str.slice(0, 20) + '\\u22ef' + str.slice(-20) + ')')\n } catch (e) {\n throw new Error('Invalid serialization format')\n }\n }\n switch (obj._serializeVerId) {\n case 'v4':\n case 'v5':\n case 'v6':\n case 'v7':\n case 'v8':\n break\n default:\n throw new Error(`Cannot unmarshal ${obj._serializeVerId} objects - please update Kvin`)\n }\n return this.unprepare([], obj.what, 'top')\n}\n\n/** Serialize a value.\n * @param what The value to serialize\n * @returns The JSON serialization of the prepared object representing what.\n */\nKVIN.prototype.serialize = function serialize (what) {\n return JSON.stringify(this.marshal(what))\n}\n\n/**\n * Serialize a value that is a Promise or contains Promises.\n *\n * @param {*} value The value to serialize\n * @returns {Promise<string>} A JSON serialization representing the value\n */\nKVIN.prototype.serializeAsync = async function serializeAsync(value) {\n return JSON.stringify(await this.marshalAsync(value))\n}\n\n/** Deserialize a value.\n * @param str The JSON serialization of the prepared object representing the value.\n * @returns The deserialized value\n */\nKVIN.prototype.deserialize = function deserialize (str) {\n if (typeof str !== 'string') {\n throw new Error(`Cannot deserialize type ${typeof str}`)\n }\n return this.unmarshal(JSON.parse(str))\n}\n\nKVIN.prototype.serializeVerId = 'v8'\n \n/* JSON-like interface */\nKVIN.prototype.parse = KVIN.prototype.deserialize; \nKVIN.prototype.stringify = KVIN.prototype.serialize;\nKVIN.prototype.stringifyAsync = KVIN.prototype.serializeAsync;\n\nexports.base_kvin = new KVIN();\n\nfor (let prop in exports.base_kvin)\n{\n if (typeof exports.base_kvin[prop] === 'function')\n exports[prop] = exports.base_kvin[prop].bind(exports);\n else {\n exports[prop] = exports.base_kvin[prop]\n }\n}\n\nexports.KVIN = KVIN;\n/* end of module */ })}\n\n\n//# sourceURL=webpack://dcp/./node_modules/kvin/kvin.js?");
|
|
3077
|
+
eval("/**\n * @file kvin.js A general-purpose library for marshaling and serializing\n * ES objects. This library is a functional superset of JSON\n * and relies on JSON for speed, adding:\n * - Typed Arrays with efficient spare representation\n * - Sparse arrays\n * - Arrays with enumerable properties\n * - Object graphs with cycles\n * - Boxed primitives (excluding Symbol)\n * - Functions (including enumerable properties, global scope)\n * - Regular Expressions\n * - undefined\n * - simple objects made with constructors\n * - objects will re-constructed with no constructor arguments\n * during deserialization\n * - enumerable properties will be copied on after construction\n * - opt-in list of supported constructors (kvin.constructorAllowList)\n *\n * This library is safe to use on user-supplied data.\n *\n * The basic implementation strategy is to marshal to an intermediate\n * format, called a 'prepared object', that can be used to recreate\n * the original object, but can also be serialized with JSON. We\n * track a list objects we have seen and their initial appearance in\n * the object graph. We rely on the /de-facto/ enumeration order of\n * properties in vanilla objects that has been present in all popular\n * browsers since the dawn of time; namely, that enumeration order is\n * object insertion order. This could cause bugs with objects with\n * cycles in less-common interpreters, such as Rhino and (especially)\n * the NJS/NGS ES3 platform by Brian Basset.\n *\n * *note* - This module free of external dependencies, and can be loaded as either a Node module,\n * a BravoJS module, or as a script tag in the browser.\n *\n * *bugs* - There are known or suspected issues in the following areas:\n * - Arrays which contain the same object more than once\n * - Arrays which mix numeric and non-numeric properties, especially if they are objects\n * - Sparse Arrays\n *\n * @author Wes Garland, wes@kingsds.network\n * @date June 2018\n *\n */\n\n\n\n{/* This prologue allows a CJS2 module's exports to be loaded with eval(readFileSync(filename)) */\n var module;\n let moduleSystemType;\n let realModule = module;\n \n if (true)\n moduleSystemType = 'webpack';\n else {}\n\n module = Object.assign({}, realModule);\n\n if (moduleSystemType === 'nodejs' || moduleSystemType === 'webpack' || moduleSystemType === 'cjs1') {\n module.declare = function kvin$$cjs1$$moduleDeclare(deps, factory) {\n factory(null, exports, null);\n module = realModule;\n return exports;\n };\n } else if (moduleSystemType === 'cjs2') {\n module = realModule;\n } else if (moduleSystemType === 'none') {\n module.declare = function kvin$$cjs1$$moduleDeclare(deps, factory) {\n let exports = {};\n factory(null, exports, null);\n module = realModule;\n\n if (typeof window === 'object')\n window.KVIN = exports;\n if (typeof globalThis === 'object')\n globalThis.KVIN = exports;\n\n return exports;\n };\n }\n/* Now initialize the module by invoking module.declare per CommonJS Modules/2.0-draft8 */\n \n/* eslint-disable indent */ module.declare([], function (require, exports, module) {\n\n/** \n * @constructor to create an alternate KVIN context. This allows us to recogonize instance of\n * the standard classes from a different JS context or have different tuning parameters. \n * @param ctors list or object of standard constructors\n */\nfunction KVIN(ctors)\n{\n // We always need to initialize the standardObjects. It is used for comparisons for primitive types etc\n this.standardObjects = {};\n for (let ctor of KVIN.prototype.ctors) {\n if (!ctor)\n continue;\n this.standardObjects[ctor.name] = ctor;\n }\n\n this.ctors = [].concat(KVIN.prototype.ctors);\n \n if (!ctors)\n return;\n\n if (Array.isArray(ctors))\n {\n for (let ctor of ctors)\n {\n if (!ctor)\n continue;\n this[ctor.name] = ctor\n for (let i=0; i < this.ctors.length; i++)\n {\n if (this.ctors[i].name === ctor.name)\n this.ctors[i] = ctor;\n }\n }\n }\n else\n {\n for (let entry of Object.entries(ctors))\n {\n for (let i=0; i < this.ctors.length; i++)\n {\n let [ name, ctor ] = entry; \n if (!ctor)\n continue;\n if (this.ctors[i].name === name)\n this.ctors[i] = ctor;\n this.standardObjects[name] = ctor;\n }\n }\n }\n}\n/*\n * Set exports.makeFunctions = true to allow deserializer to make functions.\n * If the deserializer can make functions, it is equivalent to eval() from a\n * security POV. Otherwise, the deserializer will turn functions into boxed\n * strings containing the function's source code, having a name property that\n * matches the original function.\n */\nKVIN.prototype.makeFunctions = false\n\n/* More bytes in a TypedArray than typedArrayPackThreshold will trigger\n * the code to prepare these into strings rather than arrays.\n */\nKVIN.prototype.typedArrayPackThreshold = 8\n\n/* Arrays of primitives which are >= the threshold in length are scrutinized\n * for further optimization, e.g. by run-length encoding\n */\nKVIN.prototype.scanArrayThreshold = 8\n\n\n/** Maxmimum number of arguments we can pass to a function in this engine.\n * @todo this needs to be detected at startup based on environment\n */\nconst _vm_fun_maxargs = 30000;\n\nconst littleEndian = (function () {\n let ui16 = new Uint16Array(1)\n let ui8\n\n ui16[0] = 0xffef\n ui8 = new Uint8Array(ui16.buffer, ui16.byteOffset, ui16.byteLength)\n\n if (ui8[0] === 0x0ff) {\n console.error('KVIN: Detected big-endian platform')\n return false\n }\n\n return true\n})()\n\n/** Pre-defined constructors, used to compact payload */\nKVIN.prototype.ctors = [\n Object,\n Int8Array,\n Uint8Array,\n Uint8ClampedArray,\n Int16Array,\n Uint16Array,\n Int32Array,\n Uint32Array,\n Float32Array,\n Float64Array,\n RegExp,\n Number,\n String,\n Boolean,\n Array,\n Function,\n Error,\n Promise,\n typeof URL !== 'undefined' ? URL : undefined, /* not part of ES => feature-test */\n Date,\n];\n\nKVIN.prototype.userCtors = {}; /**< name: implementation for user-defined constructors that are not props of global */\n\n/** Take a 'prepared object' (which can be represented by JSON) and turn it\n * into an object which resembles the object it was created from.\n *\n * @param seen An array objects we have already seen in this\n * object graph; used to track cycles.\n * @param po A prepared object representing a value or a primitive\n * @param position A string respresenting our position within\n * the graph. Used only for error messages.\n * @returns the value encoded by po\n */\nKVIN.prototype.unprepare = function unprepare (seen, po, position) {\n switch (typeof po) {\n case 'boolean':\n case 'number':\n case 'string':\n return po;\n }\n if (po.hasOwnProperty('ctr')) {\n switch (typeof po.ctr) {\n case 'string':\n if (!po.ctr.match(/^[A-Za-z_0-9$][A-Za-z_0-9$]*$/)) {\n if (this.constructorAllowlist && this.constructorAllowlist.indexOf(po.ctr) === -1) {\n throw new Error('Allowlist does not include constructor ' + po.ctr)\n }\n throw new Error('Invalid constructor name: ' + po.ctr)\n }\n break\n case 'number':\n if (!(po.ctr >= 0 && po.ctr < this.ctors.length)) {\n throw new Error('Invalid constructor number: ' + po.ctr)\n }\n break\n default:\n throw new Error('Invalid constructor label type ' + typeof po.ctr)\n }\n }\n if (po.hasOwnProperty('raw')) {\n if (typeof po.raw === 'object' && po.raw !== null && !Array.isArray(po.raw))\n {\n if (!po.used)\n po.used = true;\n else\n return JSON.parse(JSON.stringify(po.raw));\n return Object.assign(new this.standardObjects.Object(), po.raw);\n }\n return po.raw;\n }\n if (po.hasOwnProperty('ptv')) {\n return po.ptv; /* deprecated: only created by v3-6 */\n }\n if (po.hasOwnProperty('number')) {\n return unprepare$number(po.number);\n }\n if (po.hasOwnProperty('bigint')) {\n return unprepare$bigint(po.bigint);\n }\n if (po.hasOwnProperty('fnName')) {\n return this.unprepare$function(seen, po, position)\n }\n if (po.hasOwnProperty('mapKeys') || po.hasOwnProperty('mapVals')) {\n return this.unprepare$Map(seen, po, position)\n }\n if (po.hasOwnProperty('ab16') || po.hasOwnProperty('isl16')) {\n return this.unprepare$ArrayBuffer16(seen, po, position)\n }\n if (po.hasOwnProperty('ab8') || po.hasOwnProperty('isl8')) {\n return this.unprepare$ArrayBuffer8(seen, po, position)\n }\n if (po.hasOwnProperty('arr')) {\n return this.unprepare$Array(seen, po, position)\n }\n if (po.hasOwnProperty('ctr')) {\n return this.unprepare$object(seen, po, position)\n }\n if (po.hasOwnProperty('json')) {\n return JSON.parse(po.json)\n }\n if (po.hasOwnProperty('undefined')) {\n return undefined\n }\n\n if (Object.hasOwnProperty.call(po, 'resolve')) {\n // Unprepare a Promise by assuming po.resolve is a marshalled value.\n const promise = Promise.resolve(this.unmarshal(po.resolve));\n seen.push(promise);\n return promise;\n }\n\n if (po.hasOwnProperty('seen')) {\n if (!seen.hasOwnProperty(po.seen)) {\n throw new Error('Seen-list corruption detected at index ' + po.seen)\n }\n return seen[po.seen]\n }\n throw new TypeError('Invalid preparation formula at ' + position)\n}\n\nKVIN.prototype.unprepare$object = function unprepare$object (seen, po, position) {\n let o\n let constructor;\n\n function construct(constructor, args) {\n function fun() {\n return constructor.apply(this, args);\n }\n fun.prototype = constructor.prototype;\n return new fun();\n }\n \n if (typeof po.ctr === 'string' && !po.ctr.match(/^[1-9][0-9]*$/)) {\n if (this.userCtors.hasOwnProperty(po.ctr))\n constructor = this.userCtors[po.ctr];\n else \n constructor = eval(po.ctr) /* pre-validated! */ // eslint-disable-line\n } else {\n constructor = this.ctors[po.ctr]\n }\n\n if (po.hasOwnProperty('args'))\n o = construct(constructor, po.args);\n else if (po.hasOwnProperty('arg'))\n o = new constructor(po.arg);\n else\n o = new constructor(); // eslint-disable-line\n\n if (po.ctr === 'Error')\n {\n delete o.stack;\n delete o.lineNumber;\n delete o.fileName;\n }\n \n seen.push(o)\n\n if (po.hasOwnProperty('ps')) {\n for (let prop in po.ps) {\n if (po.ps.hasOwnProperty(prop)) {\n o[prop] = this.unprepare(seen, po.ps[prop], position + '.' + prop)\n }\n }\n }\n\n return o\n}\n\nKVIN.prototype.unprepare$function = function unprepare$function (seen, po, position) {\n let obj, fn\n let fnName = po.fnName\n\n /* A function is basically a callable object */\n po.ctr = this.ctors.indexOf(Object)\n delete po.fnName\n obj = this.unprepare(seen, po, position)\n\n if (!this.makeFunctions) {\n obj.name = fnName\n return obj\n }\n\n fn = (new Function('return ' + po.arg))() // eslint-disable-line\n if (po.hasOwnProperty('ps')) {\n for (let prop in po.ps) {\n fn[prop] = obj[prop]\n }\n }\n\n return fn\n}\n\nKVIN.prototype.unprepare$Map = function unprepare$Map (seen, po, position) {\n \n let m = new Map();\n\n seen.push(m)\n\n let mapKeyArr = this.unprepare$Array(seen, po.mapKeys, position);\n let mapValArr = this.unprepare$Array(seen, po.mapVals, position);\n\n for (let i = 0; i < mapKeyArr.length; i++)\n {\n m.set(mapKeyArr[i], mapValArr[i]);\n }\n\n if (po.hasOwnProperty('ps')) {\n for (let prop in po.ps) {\n if (po.ps.hasOwnProperty(prop)) {\n m[prop] = this.unprepare(seen, po.ps[prop], position + '.' + prop)\n }\n }\n }\n\n return m;\n}\n\nfunction unprepare$bigint(arg) {\n return BigInt(arg);\n}\n\nfunction unprepare$number(arg) {\n return parseFloat(arg);\n}\n \n/**\n * arr:[] - Array of primitives of prepared objects\n * lst:N - repeat last element N times\n * ps:[] - property list\n */\nKVIN.prototype.unprepare$Array = function unprepare$Array (seen, po, position) {\n let a = []\n let last\n\n seen.push(a)\n\n for (let i = 0; i < po.arr.length; i++) {\n if (typeof po.arr[i] === 'object') {\n if (po.arr[i].lst) {\n for (let j = 0; j < po.arr[i].lst; j++) {\n a.push(this.unprepare(seen, last, position + '.' + (i + j)))\n }\n continue\n }\n a.push(this.unprepare(seen, po.arr[i], position + '.' + i))\n last = po.arr[i]\n } else {\n a.push(po.arr[i])\n last = prepare$primitive(a[a.length-1], 'unprepare$Array')\n }\n }\n\n if (po.hasOwnProperty('isl')) {\n for (let prop in po.isl) {\n let island = po.isl[prop]\n let els = Array.isArray(island.arr) ? island.arr : this.unprepare$Array(seen, island.arr, [ position, 'isl', prop ].join('.'))\n\n if (els.length - 3 <= this.stackLimit || _vm_fun_maxargs) {\n if (els.length && (a.length < island['@'] + els.length)) {\n a.length = island['@'] + els.length\n }\n a.splice.apply(a, [island['@'], els.length].concat(els))\n } else {\n for (let i=0; i < els.length; i++) {\n a[i + +island['@']] = els[i]\n }\n }\n }\n }\n\n if (po.hasOwnProperty('ps')) {\n for (let prop in po.ps) {\n if (typeof po.ps[prop] === 'object') {\n a[prop] = this.unprepare(seen, po.ps[prop], position + '.' + prop)\n } else {\n a[prop] = po.ps[prop]\n }\n }\n }\n\n if (po.len) {\n a.length = po.len\n }\n\n return a\n}\n\n/** The ab8 (array buffer 8 bit) encoding encodes TypedArrays and related types by\n * converting them to Latin-1 strings full of binary data in 8-bit words.\n *\n * The isl8 (islands) encoding is almost the same, except that it encodes only\n * sequences of mostly-non-zero sections of the string.\n */\nKVIN.prototype.unprepare$ArrayBuffer8 = function unprepare$ArrayBuffer8 (seen, po, position) {\n let i8\n let bytes\n let constructor;\n\n if (typeof po.ctr === 'string' && !po.ctr.match(/^[1-9][0-9]*$/)) {\n constructor = eval(po.ctr) /* pre-validated! */ // eslint-disable-line\n } else {\n constructor = this.ctors[po.ctr]\n }\n\n if (po.hasOwnProperty('ab8')) {\n bytes = po.ab8.length\n } else {\n bytes = po.len\n }\n i8 = new Int8Array(bytes)\n if (po.hasOwnProperty('ab8')) {\n for (let i = 0; i < po.ab8.length; i++) {\n i8[i] = po.ab8.charCodeAt(i)\n }\n } else {\n for (let j = 0; j < po.isl8.length; j++) {\n for (let i = 0; i < po.isl8[j][0].length; i++) {\n i8[po.isl8[j]['@'] + i] = po.isl8[j][0].charCodeAt(i)\n }\n }\n }\n let o = new constructor(i8.buffer, i8.byteOffset) // eslint-disable-line;\n seen.push(o)\n return o;\n}\n\n/** The ab16 (array buffer 16 bit) encoding encodes TypedArrays and related types by\n * converting them to strings full of binary data in 16-bit words. Buffers\n * with an odd number of bytes encode an extra byte 'eb' at the end by itself.\n *\n * The isl16 (islands) encoding is almost the same, except that it encodes only\n * sequences of mostly-non-zero sections of the string.\n */\n KVIN.prototype.unprepare$ArrayBuffer16 = function unprepare$ArrayBuffer16 (seen, po, position) {\n let i16, i8, words\n let bytes\n let constructor;\n\n if (typeof po.ctr === 'string' && !po.ctr.match(/^[1-9][0-9]*$/)) {\n constructor = eval(po.ctr) /* pre-validated! */ // eslint-disable-line\n } else {\n constructor = this.ctors[po.ctr]\n }\n\n if (po.hasOwnProperty('ab16')) {\n bytes = po.ab16.length * 2\n if (po.hasOwnProperty('eb')) {\n bytes++\n }\n } else {\n bytes = po.len\n }\n\n words = Math.floor(bytes / 2) + (bytes % 2)\n i16 = new Int16Array(words)\n if (po.hasOwnProperty('ab16')) {\n for (let i = 0; i < po.ab16.length; i++) {\n i16[i] = po.ab16.charCodeAt(i)\n }\n } else {\n for (let j = 0; j < po.isl16.length; j++) {\n for (let i = 0; i < po.isl16[j][0].length; i++) {\n i16[po.isl16[j]['@'] + i] = po.isl16[j][0].charCodeAt(i)\n }\n }\n }\n i8 = new Int8Array(i16.buffer, i16.byteOffset, bytes)\n if (po.hasOwnProperty('eb')) {\n i8[i8.byteLength - 1] = po.eb.charCodeAt(0)\n }\n\n if (!littleEndian) {\n for (let i = 0; i < i8.length; i += 2) {\n i8[(i * 2) + 0] = i8[(i * 2) + 0] ^ i8[(i * 2) + 1]\n i8[(i * 2) + 1] = i8[(i * 2) + 1] ^ i8[(i * 2) + 0]\n i8[(i * 2) + 0] = i8[(i * 2) + 0] ^ i8[(i * 2) + 1]\n }\n }\n let o = new constructor(i8.buffer, i8.byteOffset) // eslint-disable-line\n seen.push(o)\n return o\n}\n\n/* Primitives and primitive-like objects do not have any special\n * marshaling requirements -- specifically, we don't need to\n * iterate over their properties in order to serialize them; we\n * can let JSON.stringify() do any heavy lifting.\n */\nKVIN.prototype.isPrimitiveLike = function isPrimitiveLike (o, seen) {\n if (o === null || typeof o === 'string' || typeof o === 'boolean')\n return true;\n\n if (typeof o === 'number')\n return Number.isFinite(o);\n\n if (typeof o !== 'object')\n return false;\n \n if (o.constructor === this.standardObjects.Object && Object.keys(o).length === 0)\n return true;\n\n if (o.constructor === this.standardObjects.Array && o.length === 0 && Object.keys(o).length === 0)\n return true;\n\n if (o.constructor !== this.standardObjects.Object && o.constructor !== this.standardObjects.Array)\n return false;\n\n if (Array.isArray(o)) {\n if (Object.keys(o).length !== o.length) {\n return false /* sparse array or named props */\n }\n }\n\n seen = seen.concat(o);\n for (let prop in o) {\n if (!o.hasOwnProperty(prop))\n return false;\n if (seen.indexOf(o[prop]) !== -1)\n return false; \n if (!this.isPrimitiveLike(o[prop], seen))\n return false;\n }\n\n return true\n}\n\n/**\n * Serialize an instance of Error, preserving standard-ish non-enumerable properties \n */ \nfunction prepare$Error(o)\n{\n let ret = {\n ctr: 'Error',\n ps: {},\n arg: o.message\n };\n\n for (let prop of ['code', 'stack', 'lineNumber', 'fileName'])\n if (o.hasOwnProperty(prop))\n ret.ps[prop] = o[prop];\n for (let prop in o)\n if (o.hasOwnProperty(prop))\n ret.ps[prop] = o[prop];\n\n return ret;\n}\n \n/** Take an arbitrary object and turn it into a 'prepared object'.\n * A prepared object can always be represented with JSON.\n *\n * @param seen An array objects we have already seen in this\n * object graph; used to track cycles.\n * @param o The object that the prepared object reflects\n * @returns A prepared object\n */\nKVIN.prototype.prepare = function prepare (seen, o, where) {\n let i, ret\n let po = {}\n\n if (typeof o === 'number') {\n return prepare$number(o)\n }\n if (typeof o === 'bigint') {\n return prepare$bigint(o)\n }\n if (this.isPrimitiveLike(o, seen)) {\n if (!Array.isArray(o) || o.length < this.scanArrayThreshold)\n return prepare$primitive(o, where)\n }\n if (typeof o === 'undefined') {\n return prepare$undefined(o)\n }\n /* value types below here can be used as targets of cycles */\n if ((i = seen.indexOf(o)) === -1) {\n seen.push(o)\n } else {\n return { seen: i }\n }\n if (Array.isArray(o)) {\n return this.prepare$Array(seen, o, where)\n }\n if (ArrayBuffer.isView(o)) {\n return this.prepare$ArrayBuffer(o)\n }\n if (o.constructor === Map) {\n return this.prepare$Map(seen, o, where)\n }\n if (o.constructor === WeakMap) {\n return prepare$WeakMap(o)\n }\n if (o.constructor === String || o.constructor === Number || o.constructor === Boolean) {\n return this.prepare$boxedPrimitive(o)\n }\n if (o.constructor === RegExp) {\n return this.prepare$RegExp(o)\n }\n\n if (o instanceof Promise || o instanceof this.standardObjects.Promise) {\n /**\n * Let the caller replace the `resolve` property with its marshalled\n * resolved value.\n */\n return { resolve: o };\n }\n\n if (o instanceof Error || o instanceof this.standardObjects.Error) {\n /* special-case Error to get non-enumerable properties */\n return prepare$Error(o);\n }\n\n if (typeof o.constructor === 'undefined') {\n console.warn('KVIN Warning: ' + where + ' is missing .constructor -- skipping')\n return prepare$undefined(o)\n }\n\n ret = { ctr: this.ctors.indexOf(o.constructor), ps: po }\n if (ret.ctr === -1) {\n /**\n * If the constructor is `Object` from another context, the indexOf check\n * would fail. So if the name of `o`'s constructor matches one of the valid\n * constructors, use the index from the mapped array to get the proper\n * constructor index.\n */\n const constructorNames = this.ctors.map((ctor) => ctor.name);\n const ctrIndex = constructorNames.indexOf(o.constructor.name);\n if (ctrIndex !== -1) {\n ret.ctr = ctrIndex;\n /**\n * Fix the `o`'s constructor to match its constructor in the current\n * context so that later equality/instanceof checks don't fail.\n */\n o.constructor = this.ctors[ctrIndex];\n } else {\n ret.ctr = o.constructor.name || this.ctors.indexOf(Object)\n }\n }\n\n if (typeof o === 'function') {\n ret.fnName = o.name\n }\n\n if (typeof o.toKVIN === 'function')\n return Object.assign(ret, o.toKVIN(o, this));\n else if (typeof o.toJSON === 'function')\n ret.arg = o.toJSON();\n else if (o.toString !== this.standardObjects.Object.prototype.toString)\n ret.arg = o.toString();\n\n if (typeof o.hasOwnProperty === 'undefined') {\n return ret;\n }\n\n /* Iterate over the properties and prepare each in turn, recursing\n * with a depth-first traversal of the object graph. Iteration order\n * must match unprepare()!\n */\n for (let prop in o) {\n if (!o.hasOwnProperty(prop)) {\n continue\n }\n\n switch (typeof o[prop]) {\n case 'function':\n case 'object':\n if (o[prop] !== null) {\n if (typeof o[prop].constructor !== 'undefined'\n && o[prop].constructor !== this.standardObjects.Object && o[prop].constructor.constructor !== this.standardObjects.Object\n && o[prop].constructor !== this.standardObjects.Function && o[prop].constructor.constructor !== this.standardObjects.Function\n && o[prop].constructor !== this.standardObjects.Function && o[prop].constructor.constructor.name !== \"Function\" /* vm context issue /wg aug 2020 */\n ) {\n throw new Error(`Cannot serialize property ${where}.${prop} - multiple inheritance is not supported.`);\n }\n if ((i = seen.indexOf(o[prop])) === -1) {\n po[prop] = this.prepare(seen, o[prop], where + '.' + prop)\n } else {\n po[prop] = { seen: i }\n }\n break\n } /* else fallthrough */\n case 'number':\n po[prop] = prepare$number(o[prop]);\n break;\n case 'bigint':\n po[prop] = prepare$bigint(o[prop]);\n break;\n case 'boolean':\n case 'string':\n po[prop] = prepare$primitive(o[prop], where + '.' + prop)\n break\n case 'undefined':\n po[prop] = prepare$undefined(o[prop])\n break\n default:\n throw new TypeError('Cannot serialize property ' + prop + ' which is a ' + typeof o[prop])\n }\n }\n\n return ret\n}\n\n/** Prepare an Array. Sparse arrays and arrays with properties\n * are supported, and represented reasonably efficiently, as are\n * arrays of repeated values.\n *\n * @param seen The current seen list for this marshal - things pointers point to\n * @param o The array we are preparing\n * @param where Human description of where we are in the object, for debugging purposes\n */\n KVIN.prototype.prepare$Array = function prepare$Array (seen, o, where) {\n let pa = { arr: [] }\n let keys = Object.keys(o)\n let lastJson = NaN\n let json\n let lstTotal = 0\n\n for (let i = 0; i < o.length; i++) {\n if (!o.hasOwnProperty(i)) {\n break /* sparse array */\n }\n if (typeof o[i] !== 'object' && this.isPrimitiveLike(o[i], seen)) {\n pa.arr.push(o[i])\n } else {\n pa.arr.push(this.prepare(seen, o[i], where + '.' + i))\n }\n\n json = JSON.stringify(pa.arr[pa.arr.length - 1])\n if (json === lastJson) {\n if (pa.arr[pa.arr.length - 2].lst) {\n pa.arr[pa.arr.length - 2].lst++\n pa.arr.length--\n lstTotal++\n } else {\n pa.arr[pa.arr.length - 1] = {lst: 1}\n }\n } else {\n lastJson = json\n }\n }\n\n if (keys.length !== o.length) {\n /* sparse array or array with own properties - difference between sparse entry and value=undefined preserved */\n for (let j = 0; j < keys.length; j++) {\n let key = keys[j]\n let idx = +key\n if (idx < j && pa.arr.hasOwnProperty(idx)) { /* test order for speed */\n continue\n }\n if (typeof idx === 'number' && o.hasOwnProperty(idx + 1)) {\n let island = { '@':idx, arr:[] }\n /* island of data inside sparse array */\n if (!pa.isl) {\n pa.isl = []\n }\n for (let k = idx; o.hasOwnProperty(k); k++) {\n island.arr.push(o[k])\n }\n j += island.arr.length - 1\n if (island.arr.length >= this.scanArrayThreshold) {\n let tmp = this.prepare(seen, island.arr, where + '.' + 'isl@' + (j - island.arr.length))\n if (tmp.hasOwnProperty('arr')) {\n island.arr = tmp\n } else {\n pa.isl.push(island)\n }\n }\n pa.isl.push(island)\n continue\n }\n if (!pa.hasOwnProperty('ps')) {\n pa.ps = {}\n }\n if (typeof o[key] !== 'object' && this.isPrimitiveLike(o[key], seen)) {\n pa.ps[key] = o[key]\n } else {\n pa.ps[key] = this.prepare(seen, o[key], where + '.' + key)\n }\n }\n }\n\n if (pa.arr.length + lstTotal !== o.length) {\n pa.len = o.length\n }\n return pa\n}\n\n/** Prepare a Map. This can be robustly handled with the existing array preparation, as\n * long as we differentiate it from normal arrays and treat keys and values separately.\n *\n * @param seen The current seen list for this marshal - things pointers point to\n * @param o The Map we are preparing\n * @param where Human description of where we are in the object, for debugging purposes\n */\n KVIN.prototype.prepare$Map = function prepare$Map (seen, o, where) {\n\n let pm = { mapKeys: [], mapVals: [] }\n\n let mapKeyArr = Array.from(o.keys());\n pm.mapKeys = this.prepare$Array(seen, mapKeyArr, where);\n\n let mapValArr = Array.from(o.values());\n pm.mapVals = this.prepare$Array(seen, mapValArr, where);\n\n let keys = Object.keys(o)\n if (keys.length !== o.length) {\n for (let j = 0; j < keys.length; j++) {\n let key = keys[j]\n if (!pm.hasOwnProperty('ps')) {\n pm.ps = {}\n }\n if (typeof o[key] !== 'object' && this.isPrimitiveLike(o[key])) {\n pm.ps[key] = o[key]\n } else {\n pm.ps[key] = this.prepare(seen, o[key], where + '.' + key)\n }\n }\n }\n\n return pm;\n}\n\n/** Prepare a WeakMap. The closest to correct behaviour for a WeakMap serialized over\n * a network is for the resulting WeakMap to be empty, since the WeakMap keys are\n * by design never directly referenced or retained, and cannot be iterated on.\n * This is why we pass an empty array to the constructor.\n *\n * @param o The WeakMap we are preparing\n */\n KVIN.prototype.prepare$WeakMap = function prepare$WeakMap (o) {\n return { ctr: ctors.indexOf(o.constructor), arg: [] }\n}\n\n/** Detect JavaScript strings which contain ill-formed UTF-16 sequences */\nfunction notUnicode(s) {\n if (/[\\ud800-\\udbff][^\\udc00-\\udfff]/.test(s)) {\n return true /* high-surrogate without low-surrogate */\n }\n\n if (/[^\\ud800-\\udbff][\\udc00-\\udfff]/.test(s)) {\n return true /* low-surrogate without high-surrogate */\n }\n\n return false\n}\n/** Prepare an ArrayBuffer into UCS-2, returning null when we cannot guarantee\n * that the UCS-2 is also composed of valid UTF-16 code points\n *\n * @see unprepare$ArrayBuffer16\n */\nKVIN.prototype.prepare$ArrayBuffer16 = function prepare$ArrayBuffer16 (o) {\n let ret = { ctr: this.ctors.indexOf(o.constructor) }\n let nWords = Math.floor(o.byteLength / 2)\n let s = ''\n\n if (ret.ctr === -1)\n ret.ctr = o.constructor.name\n\n if (littleEndian) {\n let ui16 = new Uint16Array(o.buffer, o.byteOffset, nWords)\n for (let i = 0; i < nWords; i++) {\n s += String.fromCharCode(ui16[i])\n }\n } else {\n let ui8 = new Uint8Array(o.buffer, o.byteOffset, o.byteLength)\n for (let i = 0; i < nWords; i++) {\n s += String.fromCharCode((ui8[0 + (2 * i)] << 8) + (ui8[1 + (2 * i)]))\n }\n }\n\n let manyZeroes = '\\u0000\\u0000\\u0000\\u0000'\n if (s.indexOf(manyZeroes) === -1) {\n ret.ab16 = s\n } else {\n /* String looks zero-busy: represent via islands of mostly non-zero (sparse string). */\n // let re = /([^\\u0000]+/g\n let re = /([^\\u0000]+(.{0,3}([^\\u0000]|$))*)+/g\n let island\n\n ret.isl16 = []\n ret.len = o.byteLength\n while ((island = re.exec(s))) {\n ret.isl16.push({0: island[0].replace(/\\u0000*$/, ''), '@': island.index})\n }\n }\n if ((2 * nWords) !== o.byteLength) {\n let ui8 = new Uint8Array(o.buffer, o.byteOffset + o.byteLength - 1, 1)\n ret.eb = ui8[0]\n }\n\n if (ret.ab16 && notUnicode(ret.ab16)) {\n return null\n } else if (ret.isl16) {\n for (let i = 0; i < ret.isl16.length; i++) {\n if (notUnicode(ret.isl16[i])) {\n return null\n }\n }\n }\n return ret\n}\n\n/** Encode an ArrayBuffer (TypedArray) into a string composed solely of Latin-1 characters.\n * Strings with many zeroes will be represented as sparse-string objects.\n */\nKVIN.prototype.prepare$ArrayBuffer8 = function prepare$ArrayBuffer8 (o) {\n let ret = { ctr: this.ctors.indexOf(o.constructor) }\n\n if (ret.ctr === -1)\n ret.ctr = o.constructor.name\n\n const mss = this.stackLimit || _vm_fun_maxargs - 1;\n let ui8 = new Uint8Array(o.buffer, o.byteOffset, o.byteLength)\n let segments = []\n let s\n\n for (let i=0; i < ui8.length / mss; i++) {\n segments.push(String.fromCharCode.apply(null, ui8.slice(i * mss, (i + 1) * mss)))\n }\n s = segments.join('')\n\n let manyZeroes = '\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000'\n if (s.indexOf(manyZeroes) === -1) {\n ret.ab8 = s\n } else {\n /* String looks zero-busy: represent via islands of mostly non-zero (sparse string). */\n // let re = /([^\\u0000]+/g\n let re = /([^\\u0000]+(.{0,3}([^\\u0000]|$))*)+/g\n let island\n\n ret.isl8 = []\n ret.len = o.byteLength\n while ((island = re.exec(s))) {\n ret.isl8.push({0: island[0].replace(/\\u0000*$/, ''), '@': island.index})\n }\n }\n\n return ret\n}\n\n/** Encode an ArrayBuffer (TypedArray) into a string, trying multiple methods to determine\n * optimimum size/performance. The this.tune variable affects the behaviour of this code this:\n *\n * \"speed\" - only do naive encoding: floats get represented as byte-per-digit strings\n * \"size\" - try the naive, ab8, and ab16 encodings; pick the smallest\n * neither - try the naive encoding if under typedArrayPackThreshold and use if smaller than\n * ab8; otherwise, use ab8\n */\nKVIN.prototype.prepare$ArrayBuffer = function prepare$ArrayBuffer (o) {\n let naive, naiveJSONLen;\n let ab8, ab8JSONLen;\n let ab16, ab16JSONLen;\n\n if (this.tune === \"speed\" || this.tune === \"size\" || (o.byteLength < this.typedArrayPackThreshold)) {\n naive = { ctr: this.ctors.indexOf(o.constructor), arg: Array.prototype.slice.call(o) }\n if (this.tune === \"speed\") {\n return naive\n }\n }\n\n naiveJSONLen = naive ? JSON.stringify(naive).length : Infinity\n\n ab8 = this.prepare$ArrayBuffer8(o)\n if (this.tune !== \"size\") {\n if (naive && naive.length < ab8.length) {\n return naive\n }\n return ab8\n }\n\n ab16 = this.prepare$ArrayBuffer16(o)\n ab8JSONLen = JSON.stringify(ab8).length;\n ab16JSONLen = ab16 ? JSON.stringify(ab16).length : Infinity\n\n if (ab16JSONLen < ab8JSONLen && ab16JSONLen < naiveJSONLen) {\n return ab16\n }\n if (naiveJSONLen < ab8JSONLen) {\n return naive\n }\n\n return ab8;\n}\n\nKVIN.prototype.prepare$RegExp = function prepare$RegExp (o) {\n return { ctr: this.ctors.indexOf(o.constructor), arg: o.toString().slice(1, -1) }\n}\n\nKVIN.prototype.prepare$boxedPrimitive = function prepare$boxedPrimitive (o) {\n return { ctr: this.ctors.indexOf(o.constructor), arg: o.toString() }\n}\n\nfunction prepare$bigint (n) {\n return { bigint: n.toString() }\n}\n\nfunction prepare$number (n) {\n if (!Number.isFinite(n))\n return { number: n + '' };\n\n if (1/n === -Infinity)\n return { json: \"-0\" };\n\n return n;\n}\n \n/* Store primitives and sort-of-primitives (like object literals) directly */\nfunction prepare$primitive (primitive, where) {\n switch (typeof po) {\n case 'boolean':\n case 'number': /* not all cases, see prepare$number */\n case 'string':\n return primitive;\n }\n return { raw: primitive };\n}\n\nfunction prepare$undefined (o) {\n return { undefined: true }\n}\n\n/** Prepare a value for serialization\n * @param what any (supported) js value\n * @returns an object which can be serialized with json\n */\nKVIN.prototype.marshal = function serialize$$marshal (what) {\n return {_serializeVerId: this.serializeVerId, what: this.prepare([], what, 'top')}\n}\n\n/**\n * Prepare a value that is a Promise or contains a Promise for serialization.\n *\n * Removes cycles in objects to enable stringification using `JSON.stringify`.\n *\n * @param {*} value A supported js value that can be marshalled\n * @returns {Promise<object>} An object which can be serialized with\n * `JSON.stringify`\n */\nKVIN.prototype.marshalAsync = async function serialize$$marshalAsync(value, isRecursing = false) {\n /**\n * First, have marshal memoize returned an object graph with any instances of\n * Promise found during the marshal operation with { resolve: X }, where X is\n * an instance of Promise.\n *\n * If we're recursing, we're traversing a marshaled object and shouldn't\n * redundantly marshal a nested part of it.\n */\n let marshalledObject;\n if (!isRecursing) {\n marshalledObject = this.marshal(value);\n } else {\n marshalledObject = value;\n }\n\n /**\n * Then, traverse the marshalled object, looking for these Promise memos\n * (resolve property). await the promise (X above) and replace it in the\n * marshaled object with the marshaled representation of the resolve value.\n */\n for (const key in marshalledObject) {\n if (!Object.hasOwnProperty.call(marshalledObject, key)) {\n continue;\n }\n\n switch (typeof marshalledObject[key]) {\n case 'object':\n if (marshalledObject[key] === null) {\n continue;\n }\n\n if (\n typeof marshalledObject[key].resolve !== 'undefined' &&\n marshalledObject[key].resolve instanceof Promise\n ) {\n marshalledObject[key].resolve = await this.marshalAsync(\n await marshalledObject[key].resolve,\n );\n }\n\n /**\n * Recursively traverse the marshalled object\n *\n * Operating on the marshalled object graph means we know for certain we\n * are working on a directed acyclic graph (DAG); prepares's \"seen\"\n * array argument expresses cycles separately.\n */\n marshalledObject[key] = await this.marshalAsync(\n marshalledObject[key],\n true,\n );\n break;\n default:\n break;\n }\n }\n\n return marshalledObject;\n}\n\n/** Turn a marshaled (prepared) value back into its original form\n * @param obj a prepared object - the output of this.marshal()\n * @returns object an object resembling the object originally passed to this.marshal()\n */\nKVIN.prototype.unmarshal = function serialize$$unmarshal (obj) {\n if (typeof obj !== 'object' || obj === null) {\n throw new Error(`Cannot unmarshal type ${typeof obj} or null.`)\n }\n if (!obj.hasOwnProperty('_serializeVerId')) {\n try {\n let str = JSON.stringify(obj)\n throw new Error('Invalid serialization format (' + str.slice(0, 20) + '\\u22ef' + str.slice(-20) + ')')\n } catch (e) {\n throw new Error('Invalid serialization format')\n }\n }\n switch (obj._serializeVerId) {\n case 'v4':\n case 'v5':\n case 'v6':\n case 'v7':\n case 'v8':\n break\n default:\n throw new Error(`Cannot unmarshal ${obj._serializeVerId} objects - please update Kvin`)\n }\n return this.unprepare([], obj.what, 'top')\n}\n\n/** Serialize a value.\n * @param what The value to serialize\n * @returns The JSON serialization of the prepared object representing what.\n */\nKVIN.prototype.serialize = function serialize (what) {\n return JSON.stringify(this.marshal(what))\n}\n\n/**\n * Serialize a value that is a Promise or contains Promises.\n *\n * @param {*} value The value to serialize\n * @returns {Promise<string>} A JSON serialization representing the value\n */\nKVIN.prototype.serializeAsync = async function serializeAsync(value) {\n return JSON.stringify(await this.marshalAsync(value))\n}\n\n/** Deserialize a value.\n * @param str The JSON serialization of the prepared object representing the value.\n * @returns The deserialized value\n */\nKVIN.prototype.deserialize = function deserialize (str) {\n if (typeof str !== 'string') {\n throw new Error(`Cannot deserialize type ${typeof str}`)\n }\n return this.unmarshal(JSON.parse(str))\n}\n\nKVIN.prototype.serializeVerId = 'v8'\n \n/* JSON-like interface */\nKVIN.prototype.parse = KVIN.prototype.deserialize; \nKVIN.prototype.stringify = KVIN.prototype.serialize;\nKVIN.prototype.stringifyAsync = KVIN.prototype.serializeAsync;\n\nexports.base_kvin = new KVIN();\n\nfor (let prop in exports.base_kvin)\n{\n if (typeof exports.base_kvin[prop] === 'function')\n exports[prop] = exports.base_kvin[prop].bind(exports);\n else {\n exports[prop] = exports.base_kvin[prop]\n }\n}\n\nexports.KVIN = KVIN;\n/* end of module */ })}\n\n\n//# sourceURL=webpack://dcp/./node_modules/kvin/kvin.js?");
|
|
3045
3078
|
|
|
3046
3079
|
/***/ }),
|
|
3047
3080
|
|
|
@@ -3108,6 +3141,16 @@ eval("\n\nvar utils = exports;\n\nfunction toArray(msg, enc) {\n if (Array.isAr
|
|
|
3108
3141
|
|
|
3109
3142
|
/***/ }),
|
|
3110
3143
|
|
|
3144
|
+
/***/ "./node_modules/moo/moo.js":
|
|
3145
|
+
/*!*********************************!*\
|
|
3146
|
+
!*** ./node_modules/moo/moo.js ***!
|
|
3147
|
+
\*********************************/
|
|
3148
|
+
/***/ (function(module, exports) {
|
|
3149
|
+
|
|
3150
|
+
eval("var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;(function(root, factory) {\n if (true) {\n !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory),\n\t\t__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?\n\t\t(__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__),\n\t\t__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)) /* global define */\n } else {}\n}(this, function() {\n 'use strict';\n\n var hasOwnProperty = Object.prototype.hasOwnProperty\n var toString = Object.prototype.toString\n var hasSticky = typeof new RegExp().sticky === 'boolean'\n\n /***************************************************************************/\n\n function isRegExp(o) { return o && toString.call(o) === '[object RegExp]' }\n function isObject(o) { return o && typeof o === 'object' && !isRegExp(o) && !Array.isArray(o) }\n\n function reEscape(s) {\n return s.replace(/[-\\/\\\\^$*+?.()|[\\]{}]/g, '\\\\$&')\n }\n function reGroups(s) {\n var re = new RegExp('|' + s)\n return re.exec('').length - 1\n }\n function reCapture(s) {\n return '(' + s + ')'\n }\n function reUnion(regexps) {\n if (!regexps.length) return '(?!)'\n var source = regexps.map(function(s) {\n return \"(?:\" + s + \")\"\n }).join('|')\n return \"(?:\" + source + \")\"\n }\n\n function regexpOrLiteral(obj) {\n if (typeof obj === 'string') {\n return '(?:' + reEscape(obj) + ')'\n\n } else if (isRegExp(obj)) {\n // TODO: consider /u support\n if (obj.ignoreCase) throw new Error('RegExp /i flag not allowed')\n if (obj.global) throw new Error('RegExp /g flag is implied')\n if (obj.sticky) throw new Error('RegExp /y flag is implied')\n if (obj.multiline) throw new Error('RegExp /m flag is implied')\n return obj.source\n\n } else {\n throw new Error('Not a pattern: ' + obj)\n }\n }\n\n function pad(s, length) {\n if (s.length > length) {\n return s\n }\n return Array(length - s.length + 1).join(\" \") + s\n }\n\n function lastNLines(string, numLines) {\n var position = string.length\n var lineBreaks = 0;\n while (true) {\n var idx = string.lastIndexOf(\"\\n\", position - 1)\n if (idx === -1) {\n break;\n } else {\n lineBreaks++\n }\n position = idx\n if (lineBreaks === numLines) {\n break;\n }\n if (position === 0) {\n break;\n }\n }\n var startPosition = \n lineBreaks < numLines ?\n 0 : \n position + 1\n return string.substring(startPosition).split(\"\\n\")\n }\n\n function objectToRules(object) {\n var keys = Object.getOwnPropertyNames(object)\n var result = []\n for (var i = 0; i < keys.length; i++) {\n var key = keys[i]\n var thing = object[key]\n var rules = [].concat(thing)\n if (key === 'include') {\n for (var j = 0; j < rules.length; j++) {\n result.push({include: rules[j]})\n }\n continue\n }\n var match = []\n rules.forEach(function(rule) {\n if (isObject(rule)) {\n if (match.length) result.push(ruleOptions(key, match))\n result.push(ruleOptions(key, rule))\n match = []\n } else {\n match.push(rule)\n }\n })\n if (match.length) result.push(ruleOptions(key, match))\n }\n return result\n }\n\n function arrayToRules(array) {\n var result = []\n for (var i = 0; i < array.length; i++) {\n var obj = array[i]\n if (obj.include) {\n var include = [].concat(obj.include)\n for (var j = 0; j < include.length; j++) {\n result.push({include: include[j]})\n }\n continue\n }\n if (!obj.type) {\n throw new Error('Rule has no type: ' + JSON.stringify(obj))\n }\n result.push(ruleOptions(obj.type, obj))\n }\n return result\n }\n\n function ruleOptions(type, obj) {\n if (!isObject(obj)) {\n obj = { match: obj }\n }\n if (obj.include) {\n throw new Error('Matching rules cannot also include states')\n }\n\n // nb. error and fallback imply lineBreaks\n var options = {\n defaultType: type,\n lineBreaks: !!obj.error || !!obj.fallback,\n pop: false,\n next: null,\n push: null,\n error: false,\n fallback: false,\n value: null,\n type: null,\n shouldThrow: false,\n }\n\n // Avoid Object.assign(), so we support IE9+\n for (var key in obj) {\n if (hasOwnProperty.call(obj, key)) {\n options[key] = obj[key]\n }\n }\n\n // type transform cannot be a string\n if (typeof options.type === 'string' && type !== options.type) {\n throw new Error(\"Type transform cannot be a string (type '\" + options.type + \"' for token '\" + type + \"')\")\n }\n\n // convert to array\n var match = options.match\n options.match = Array.isArray(match) ? match : match ? [match] : []\n options.match.sort(function(a, b) {\n return isRegExp(a) && isRegExp(b) ? 0\n : isRegExp(b) ? -1 : isRegExp(a) ? +1 : b.length - a.length\n })\n return options\n }\n\n function toRules(spec) {\n return Array.isArray(spec) ? arrayToRules(spec) : objectToRules(spec)\n }\n\n var defaultErrorRule = ruleOptions('error', {lineBreaks: true, shouldThrow: true})\n function compileRules(rules, hasStates) {\n var errorRule = null\n var fast = Object.create(null)\n var fastAllowed = true\n var unicodeFlag = null\n var groups = []\n var parts = []\n\n // If there is a fallback rule, then disable fast matching\n for (var i = 0; i < rules.length; i++) {\n if (rules[i].fallback) {\n fastAllowed = false\n }\n }\n\n for (var i = 0; i < rules.length; i++) {\n var options = rules[i]\n\n if (options.include) {\n // all valid inclusions are removed by states() preprocessor\n throw new Error('Inheritance is not allowed in stateless lexers')\n }\n\n if (options.error || options.fallback) {\n // errorRule can only be set once\n if (errorRule) {\n if (!options.fallback === !errorRule.fallback) {\n throw new Error(\"Multiple \" + (options.fallback ? \"fallback\" : \"error\") + \" rules not allowed (for token '\" + options.defaultType + \"')\")\n } else {\n throw new Error(\"fallback and error are mutually exclusive (for token '\" + options.defaultType + \"')\")\n }\n }\n errorRule = options\n }\n\n var match = options.match.slice()\n if (fastAllowed) {\n while (match.length && typeof match[0] === 'string' && match[0].length === 1) {\n var word = match.shift()\n fast[word.charCodeAt(0)] = options\n }\n }\n\n // Warn about inappropriate state-switching options\n if (options.pop || options.push || options.next) {\n if (!hasStates) {\n throw new Error(\"State-switching options are not allowed in stateless lexers (for token '\" + options.defaultType + \"')\")\n }\n if (options.fallback) {\n throw new Error(\"State-switching options are not allowed on fallback tokens (for token '\" + options.defaultType + \"')\")\n }\n }\n\n // Only rules with a .match are included in the RegExp\n if (match.length === 0) {\n continue\n }\n fastAllowed = false\n\n groups.push(options)\n\n // Check unicode flag is used everywhere or nowhere\n for (var j = 0; j < match.length; j++) {\n var obj = match[j]\n if (!isRegExp(obj)) {\n continue\n }\n\n if (unicodeFlag === null) {\n unicodeFlag = obj.unicode\n } else if (unicodeFlag !== obj.unicode && options.fallback === false) {\n throw new Error('If one rule is /u then all must be')\n }\n }\n\n // convert to RegExp\n var pat = reUnion(match.map(regexpOrLiteral))\n\n // validate\n var regexp = new RegExp(pat)\n if (regexp.test(\"\")) {\n throw new Error(\"RegExp matches empty string: \" + regexp)\n }\n var groupCount = reGroups(pat)\n if (groupCount > 0) {\n throw new Error(\"RegExp has capture groups: \" + regexp + \"\\nUse (?: … ) instead\")\n }\n\n // try and detect rules matching newlines\n if (!options.lineBreaks && regexp.test('\\n')) {\n throw new Error('Rule should declare lineBreaks: ' + regexp)\n }\n\n // store regex\n parts.push(reCapture(pat))\n }\n\n\n // If there's no fallback rule, use the sticky flag so we only look for\n // matches at the current index.\n //\n // If we don't support the sticky flag, then fake it using an irrefutable\n // match (i.e. an empty pattern).\n var fallbackRule = errorRule && errorRule.fallback\n var flags = hasSticky && !fallbackRule ? 'ym' : 'gm'\n var suffix = hasSticky || fallbackRule ? '' : '|'\n\n if (unicodeFlag === true) flags += \"u\"\n var combined = new RegExp(reUnion(parts) + suffix, flags)\n return {regexp: combined, groups: groups, fast: fast, error: errorRule || defaultErrorRule}\n }\n\n function compile(rules) {\n var result = compileRules(toRules(rules))\n return new Lexer({start: result}, 'start')\n }\n\n function checkStateGroup(g, name, map) {\n var state = g && (g.push || g.next)\n if (state && !map[state]) {\n throw new Error(\"Missing state '\" + state + \"' (in token '\" + g.defaultType + \"' of state '\" + name + \"')\")\n }\n if (g && g.pop && +g.pop !== 1) {\n throw new Error(\"pop must be 1 (in token '\" + g.defaultType + \"' of state '\" + name + \"')\")\n }\n }\n function compileStates(states, start) {\n var all = states.$all ? toRules(states.$all) : []\n delete states.$all\n\n var keys = Object.getOwnPropertyNames(states)\n if (!start) start = keys[0]\n\n var ruleMap = Object.create(null)\n for (var i = 0; i < keys.length; i++) {\n var key = keys[i]\n ruleMap[key] = toRules(states[key]).concat(all)\n }\n for (var i = 0; i < keys.length; i++) {\n var key = keys[i]\n var rules = ruleMap[key]\n var included = Object.create(null)\n for (var j = 0; j < rules.length; j++) {\n var rule = rules[j]\n if (!rule.include) continue\n var splice = [j, 1]\n if (rule.include !== key && !included[rule.include]) {\n included[rule.include] = true\n var newRules = ruleMap[rule.include]\n if (!newRules) {\n throw new Error(\"Cannot include nonexistent state '\" + rule.include + \"' (in state '\" + key + \"')\")\n }\n for (var k = 0; k < newRules.length; k++) {\n var newRule = newRules[k]\n if (rules.indexOf(newRule) !== -1) continue\n splice.push(newRule)\n }\n }\n rules.splice.apply(rules, splice)\n j--\n }\n }\n\n var map = Object.create(null)\n for (var i = 0; i < keys.length; i++) {\n var key = keys[i]\n map[key] = compileRules(ruleMap[key], true)\n }\n\n for (var i = 0; i < keys.length; i++) {\n var name = keys[i]\n var state = map[name]\n var groups = state.groups\n for (var j = 0; j < groups.length; j++) {\n checkStateGroup(groups[j], name, map)\n }\n var fastKeys = Object.getOwnPropertyNames(state.fast)\n for (var j = 0; j < fastKeys.length; j++) {\n checkStateGroup(state.fast[fastKeys[j]], name, map)\n }\n }\n\n return new Lexer(map, start)\n }\n\n function keywordTransform(map) {\n\n // Use a JavaScript Map to map keywords to their corresponding token type\n // unless Map is unsupported, then fall back to using an Object:\n var isMap = typeof Map !== 'undefined'\n var reverseMap = isMap ? new Map : Object.create(null)\n\n var types = Object.getOwnPropertyNames(map)\n for (var i = 0; i < types.length; i++) {\n var tokenType = types[i]\n var item = map[tokenType]\n var keywordList = Array.isArray(item) ? item : [item]\n keywordList.forEach(function(keyword) {\n if (typeof keyword !== 'string') {\n throw new Error(\"keyword must be string (in keyword '\" + tokenType + \"')\")\n }\n if (isMap) {\n reverseMap.set(keyword, tokenType)\n } else {\n reverseMap[keyword] = tokenType\n }\n })\n }\n return function(k) {\n return isMap ? reverseMap.get(k) : reverseMap[k]\n }\n }\n\n /***************************************************************************/\n\n var Lexer = function(states, state) {\n this.startState = state\n this.states = states\n this.buffer = ''\n this.stack = []\n this.reset()\n }\n\n Lexer.prototype.reset = function(data, info) {\n this.buffer = data || ''\n this.index = 0\n this.line = info ? info.line : 1\n this.col = info ? info.col : 1\n this.queuedToken = info ? info.queuedToken : null\n this.queuedText = info ? info.queuedText: \"\";\n this.queuedThrow = info ? info.queuedThrow : null\n this.setState(info ? info.state : this.startState)\n this.stack = info && info.stack ? info.stack.slice() : []\n return this\n }\n\n Lexer.prototype.save = function() {\n return {\n line: this.line,\n col: this.col,\n state: this.state,\n stack: this.stack.slice(),\n queuedToken: this.queuedToken,\n queuedText: this.queuedText,\n queuedThrow: this.queuedThrow,\n }\n }\n\n Lexer.prototype.setState = function(state) {\n if (!state || this.state === state) return\n this.state = state\n var info = this.states[state]\n this.groups = info.groups\n this.error = info.error\n this.re = info.regexp\n this.fast = info.fast\n }\n\n Lexer.prototype.popState = function() {\n this.setState(this.stack.pop())\n }\n\n Lexer.prototype.pushState = function(state) {\n this.stack.push(this.state)\n this.setState(state)\n }\n\n var eat = hasSticky ? function(re, buffer) { // assume re is /y\n return re.exec(buffer)\n } : function(re, buffer) { // assume re is /g\n var match = re.exec(buffer)\n // will always match, since we used the |(?:) trick\n if (match[0].length === 0) {\n return null\n }\n return match\n }\n\n Lexer.prototype._getGroup = function(match) {\n var groupCount = this.groups.length\n for (var i = 0; i < groupCount; i++) {\n if (match[i + 1] !== undefined) {\n return this.groups[i]\n }\n }\n throw new Error('Cannot find token type for matched text')\n }\n\n function tokenToString() {\n return this.value\n }\n\n Lexer.prototype.next = function() {\n var index = this.index\n\n // If a fallback token matched, we don't need to re-run the RegExp\n if (this.queuedGroup) {\n var token = this._token(this.queuedGroup, this.queuedText, index)\n this.queuedGroup = null\n this.queuedText = \"\"\n return token\n }\n\n var buffer = this.buffer\n if (index === buffer.length) {\n return // EOF\n }\n\n // Fast matching for single characters\n var group = this.fast[buffer.charCodeAt(index)]\n if (group) {\n return this._token(group, buffer.charAt(index), index)\n }\n\n // Execute RegExp\n var re = this.re\n re.lastIndex = index\n var match = eat(re, buffer)\n\n // Error tokens match the remaining buffer\n var error = this.error\n if (match == null) {\n return this._token(error, buffer.slice(index, buffer.length), index)\n }\n\n var group = this._getGroup(match)\n var text = match[0]\n\n if (error.fallback && match.index !== index) {\n this.queuedGroup = group\n this.queuedText = text\n\n // Fallback tokens contain the unmatched portion of the buffer\n return this._token(error, buffer.slice(index, match.index), index)\n }\n\n return this._token(group, text, index)\n }\n\n Lexer.prototype._token = function(group, text, offset) {\n // count line breaks\n var lineBreaks = 0\n if (group.lineBreaks) {\n var matchNL = /\\n/g\n var nl = 1\n if (text === '\\n') {\n lineBreaks = 1\n } else {\n while (matchNL.exec(text)) { lineBreaks++; nl = matchNL.lastIndex }\n }\n }\n\n var token = {\n type: (typeof group.type === 'function' && group.type(text)) || group.defaultType,\n value: typeof group.value === 'function' ? group.value(text) : text,\n text: text,\n toString: tokenToString,\n offset: offset,\n lineBreaks: lineBreaks,\n line: this.line,\n col: this.col,\n }\n // nb. adding more props to token object will make V8 sad!\n\n var size = text.length\n this.index += size\n this.line += lineBreaks\n if (lineBreaks !== 0) {\n this.col = size - nl + 1\n } else {\n this.col += size\n }\n\n // throw, if no rule with {error: true}\n if (group.shouldThrow) {\n var err = new Error(this.formatError(token, \"invalid syntax\"))\n throw err;\n }\n\n if (group.pop) this.popState()\n else if (group.push) this.pushState(group.push)\n else if (group.next) this.setState(group.next)\n\n return token\n }\n\n if (typeof Symbol !== 'undefined' && Symbol.iterator) {\n var LexerIterator = function(lexer) {\n this.lexer = lexer\n }\n\n LexerIterator.prototype.next = function() {\n var token = this.lexer.next()\n return {value: token, done: !token}\n }\n\n LexerIterator.prototype[Symbol.iterator] = function() {\n return this\n }\n\n Lexer.prototype[Symbol.iterator] = function() {\n return new LexerIterator(this)\n }\n }\n\n Lexer.prototype.formatError = function(token, message) {\n if (token == null) {\n // An undefined token indicates EOF\n var text = this.buffer.slice(this.index)\n var token = {\n text: text,\n offset: this.index,\n lineBreaks: text.indexOf('\\n') === -1 ? 0 : 1,\n line: this.line,\n col: this.col,\n }\n }\n \n var numLinesAround = 2\n var firstDisplayedLine = Math.max(token.line - numLinesAround, 1)\n var lastDisplayedLine = token.line + numLinesAround\n var lastLineDigits = String(lastDisplayedLine).length\n var displayedLines = lastNLines(\n this.buffer, \n (this.line - token.line) + numLinesAround + 1\n )\n .slice(0, 5)\n var errorLines = []\n errorLines.push(message + \" at line \" + token.line + \" col \" + token.col + \":\")\n errorLines.push(\"\")\n for (var i = 0; i < displayedLines.length; i++) {\n var line = displayedLines[i]\n var lineNo = firstDisplayedLine + i\n errorLines.push(pad(String(lineNo), lastLineDigits) + \" \" + line);\n if (lineNo === token.line) {\n errorLines.push(pad(\"\", lastLineDigits + token.col + 1) + \"^\")\n }\n }\n return errorLines.join(\"\\n\")\n }\n\n Lexer.prototype.clone = function() {\n return new Lexer(this.states, this.state)\n }\n\n Lexer.prototype.has = function(tokenType) {\n return true\n }\n\n\n return {\n compile: compile,\n states: compileStates,\n error: Object.freeze({error: true}),\n fallback: Object.freeze({fallback: true}),\n keywords: keywordTransform,\n }\n\n}));\n\n\n//# sourceURL=webpack://dcp/./node_modules/moo/moo.js?");
|
|
3151
|
+
|
|
3152
|
+
/***/ }),
|
|
3153
|
+
|
|
3111
3154
|
/***/ "./node_modules/ms/index.js":
|
|
3112
3155
|
/*!**********************************!*\
|
|
3113
3156
|
!*** ./node_modules/ms/index.js ***!
|
|
@@ -3118,6 +3161,16 @@ eval("/**\n * Helpers.\n */\n\nvar s = 1000;\nvar m = s * 60;\nvar h = m * 60;\n
|
|
|
3118
3161
|
|
|
3119
3162
|
/***/ }),
|
|
3120
3163
|
|
|
3164
|
+
/***/ "./node_modules/nearley/lib/nearley.js":
|
|
3165
|
+
/*!*********************************************!*\
|
|
3166
|
+
!*** ./node_modules/nearley/lib/nearley.js ***!
|
|
3167
|
+
\*********************************************/
|
|
3168
|
+
/***/ (function(module) {
|
|
3169
|
+
|
|
3170
|
+
eval("(function(root, factory) {\n if ( true && module.exports) {\n module.exports = factory();\n } else {\n root.nearley = factory();\n }\n}(this, function() {\n\n function Rule(name, symbols, postprocess) {\n this.id = ++Rule.highestId;\n this.name = name;\n this.symbols = symbols; // a list of literal | regex class | nonterminal\n this.postprocess = postprocess;\n return this;\n }\n Rule.highestId = 0;\n\n Rule.prototype.toString = function(withCursorAt) {\n var symbolSequence = (typeof withCursorAt === \"undefined\")\n ? this.symbols.map(getSymbolShortDisplay).join(' ')\n : ( this.symbols.slice(0, withCursorAt).map(getSymbolShortDisplay).join(' ')\n + \" ● \"\n + this.symbols.slice(withCursorAt).map(getSymbolShortDisplay).join(' ') );\n return this.name + \" → \" + symbolSequence;\n }\n\n\n // a State is a rule at a position from a given starting point in the input stream (reference)\n function State(rule, dot, reference, wantedBy) {\n this.rule = rule;\n this.dot = dot;\n this.reference = reference;\n this.data = [];\n this.wantedBy = wantedBy;\n this.isComplete = this.dot === rule.symbols.length;\n }\n\n State.prototype.toString = function() {\n return \"{\" + this.rule.toString(this.dot) + \"}, from: \" + (this.reference || 0);\n };\n\n State.prototype.nextState = function(child) {\n var state = new State(this.rule, this.dot + 1, this.reference, this.wantedBy);\n state.left = this;\n state.right = child;\n if (state.isComplete) {\n state.data = state.build();\n // Having right set here will prevent the right state and its children\n // form being garbage collected\n state.right = undefined;\n }\n return state;\n };\n\n State.prototype.build = function() {\n var children = [];\n var node = this;\n do {\n children.push(node.right.data);\n node = node.left;\n } while (node.left);\n children.reverse();\n return children;\n };\n\n State.prototype.finish = function() {\n if (this.rule.postprocess) {\n this.data = this.rule.postprocess(this.data, this.reference, Parser.fail);\n }\n };\n\n\n function Column(grammar, index) {\n this.grammar = grammar;\n this.index = index;\n this.states = [];\n this.wants = {}; // states indexed by the non-terminal they expect\n this.scannable = []; // list of states that expect a token\n this.completed = {}; // states that are nullable\n }\n\n\n Column.prototype.process = function(nextColumn) {\n var states = this.states;\n var wants = this.wants;\n var completed = this.completed;\n\n for (var w = 0; w < states.length; w++) { // nb. we push() during iteration\n var state = states[w];\n\n if (state.isComplete) {\n state.finish();\n if (state.data !== Parser.fail) {\n // complete\n var wantedBy = state.wantedBy;\n for (var i = wantedBy.length; i--; ) { // this line is hot\n var left = wantedBy[i];\n this.complete(left, state);\n }\n\n // special-case nullables\n if (state.reference === this.index) {\n // make sure future predictors of this rule get completed.\n var exp = state.rule.name;\n (this.completed[exp] = this.completed[exp] || []).push(state);\n }\n }\n\n } else {\n // queue scannable states\n var exp = state.rule.symbols[state.dot];\n if (typeof exp !== 'string') {\n this.scannable.push(state);\n continue;\n }\n\n // predict\n if (wants[exp]) {\n wants[exp].push(state);\n\n if (completed.hasOwnProperty(exp)) {\n var nulls = completed[exp];\n for (var i = 0; i < nulls.length; i++) {\n var right = nulls[i];\n this.complete(state, right);\n }\n }\n } else {\n wants[exp] = [state];\n this.predict(exp);\n }\n }\n }\n }\n\n Column.prototype.predict = function(exp) {\n var rules = this.grammar.byName[exp] || [];\n\n for (var i = 0; i < rules.length; i++) {\n var r = rules[i];\n var wantedBy = this.wants[exp];\n var s = new State(r, 0, this.index, wantedBy);\n this.states.push(s);\n }\n }\n\n Column.prototype.complete = function(left, right) {\n var copy = left.nextState(right);\n this.states.push(copy);\n }\n\n\n function Grammar(rules, start) {\n this.rules = rules;\n this.start = start || this.rules[0].name;\n var byName = this.byName = {};\n this.rules.forEach(function(rule) {\n if (!byName.hasOwnProperty(rule.name)) {\n byName[rule.name] = [];\n }\n byName[rule.name].push(rule);\n });\n }\n\n // So we can allow passing (rules, start) directly to Parser for backwards compatibility\n Grammar.fromCompiled = function(rules, start) {\n var lexer = rules.Lexer;\n if (rules.ParserStart) {\n start = rules.ParserStart;\n rules = rules.ParserRules;\n }\n var rules = rules.map(function (r) { return (new Rule(r.name, r.symbols, r.postprocess)); });\n var g = new Grammar(rules, start);\n g.lexer = lexer; // nb. storing lexer on Grammar is iffy, but unavoidable\n return g;\n }\n\n\n function StreamLexer() {\n this.reset(\"\");\n }\n\n StreamLexer.prototype.reset = function(data, state) {\n this.buffer = data;\n this.index = 0;\n this.line = state ? state.line : 1;\n this.lastLineBreak = state ? -state.col : 0;\n }\n\n StreamLexer.prototype.next = function() {\n if (this.index < this.buffer.length) {\n var ch = this.buffer[this.index++];\n if (ch === '\\n') {\n this.line += 1;\n this.lastLineBreak = this.index;\n }\n return {value: ch};\n }\n }\n\n StreamLexer.prototype.save = function() {\n return {\n line: this.line,\n col: this.index - this.lastLineBreak,\n }\n }\n\n StreamLexer.prototype.formatError = function(token, message) {\n // nb. this gets called after consuming the offending token,\n // so the culprit is index-1\n var buffer = this.buffer;\n if (typeof buffer === 'string') {\n var lines = buffer\n .split(\"\\n\")\n .slice(\n Math.max(0, this.line - 5), \n this.line\n );\n\n var nextLineBreak = buffer.indexOf('\\n', this.index);\n if (nextLineBreak === -1) nextLineBreak = buffer.length;\n var col = this.index - this.lastLineBreak;\n var lastLineDigits = String(this.line).length;\n message += \" at line \" + this.line + \" col \" + col + \":\\n\\n\";\n message += lines\n .map(function(line, i) {\n return pad(this.line - lines.length + i + 1, lastLineDigits) + \" \" + line;\n }, this)\n .join(\"\\n\");\n message += \"\\n\" + pad(\"\", lastLineDigits + col) + \"^\\n\";\n return message;\n } else {\n return message + \" at index \" + (this.index - 1);\n }\n\n function pad(n, length) {\n var s = String(n);\n return Array(length - s.length + 1).join(\" \") + s;\n }\n }\n\n function Parser(rules, start, options) {\n if (rules instanceof Grammar) {\n var grammar = rules;\n var options = start;\n } else {\n var grammar = Grammar.fromCompiled(rules, start);\n }\n this.grammar = grammar;\n\n // Read options\n this.options = {\n keepHistory: false,\n lexer: grammar.lexer || new StreamLexer,\n };\n for (var key in (options || {})) {\n this.options[key] = options[key];\n }\n\n // Setup lexer\n this.lexer = this.options.lexer;\n this.lexerState = undefined;\n\n // Setup a table\n var column = new Column(grammar, 0);\n var table = this.table = [column];\n\n // I could be expecting anything.\n column.wants[grammar.start] = [];\n column.predict(grammar.start);\n // TODO what if start rule is nullable?\n column.process();\n this.current = 0; // token index\n }\n\n // create a reserved token for indicating a parse fail\n Parser.fail = {};\n\n Parser.prototype.feed = function(chunk) {\n var lexer = this.lexer;\n lexer.reset(chunk, this.lexerState);\n\n var token;\n while (true) {\n try {\n token = lexer.next();\n if (!token) {\n break;\n }\n } catch (e) {\n // Create the next column so that the error reporter\n // can display the correctly predicted states.\n var nextColumn = new Column(this.grammar, this.current + 1);\n this.table.push(nextColumn);\n var err = new Error(this.reportLexerError(e));\n err.offset = this.current;\n err.token = e.token;\n throw err;\n }\n // We add new states to table[current+1]\n var column = this.table[this.current];\n\n // GC unused states\n if (!this.options.keepHistory) {\n delete this.table[this.current - 1];\n }\n\n var n = this.current + 1;\n var nextColumn = new Column(this.grammar, n);\n this.table.push(nextColumn);\n\n // Advance all tokens that expect the symbol\n var literal = token.text !== undefined ? token.text : token.value;\n var value = lexer.constructor === StreamLexer ? token.value : token;\n var scannable = column.scannable;\n for (var w = scannable.length; w--; ) {\n var state = scannable[w];\n var expect = state.rule.symbols[state.dot];\n // Try to consume the token\n // either regex or literal\n if (expect.test ? expect.test(value) :\n expect.type ? expect.type === token.type\n : expect.literal === literal) {\n // Add it\n var next = state.nextState({data: value, token: token, isToken: true, reference: n - 1});\n nextColumn.states.push(next);\n }\n }\n\n // Next, for each of the rules, we either\n // (a) complete it, and try to see if the reference row expected that\n // rule\n // (b) predict the next nonterminal it expects by adding that\n // nonterminal's start state\n // To prevent duplication, we also keep track of rules we have already\n // added\n\n nextColumn.process();\n\n // If needed, throw an error:\n if (nextColumn.states.length === 0) {\n // No states at all! This is not good.\n var err = new Error(this.reportError(token));\n err.offset = this.current;\n err.token = token;\n throw err;\n }\n\n // maybe save lexer state\n if (this.options.keepHistory) {\n column.lexerState = lexer.save()\n }\n\n this.current++;\n }\n if (column) {\n this.lexerState = lexer.save()\n }\n\n // Incrementally keep track of results\n this.results = this.finish();\n\n // Allow chaining, for whatever it's worth\n return this;\n };\n\n Parser.prototype.reportLexerError = function(lexerError) {\n var tokenDisplay, lexerMessage;\n // Planning to add a token property to moo's thrown error\n // even on erroring tokens to be used in error display below\n var token = lexerError.token;\n if (token) {\n tokenDisplay = \"input \" + JSON.stringify(token.text[0]) + \" (lexer error)\";\n lexerMessage = this.lexer.formatError(token, \"Syntax error\");\n } else {\n tokenDisplay = \"input (lexer error)\";\n lexerMessage = lexerError.message;\n }\n return this.reportErrorCommon(lexerMessage, tokenDisplay);\n };\n\n Parser.prototype.reportError = function(token) {\n var tokenDisplay = (token.type ? token.type + \" token: \" : \"\") + JSON.stringify(token.value !== undefined ? token.value : token);\n var lexerMessage = this.lexer.formatError(token, \"Syntax error\");\n return this.reportErrorCommon(lexerMessage, tokenDisplay);\n };\n\n Parser.prototype.reportErrorCommon = function(lexerMessage, tokenDisplay) {\n var lines = [];\n lines.push(lexerMessage);\n var lastColumnIndex = this.table.length - 2;\n var lastColumn = this.table[lastColumnIndex];\n var expectantStates = lastColumn.states\n .filter(function(state) {\n var nextSymbol = state.rule.symbols[state.dot];\n return nextSymbol && typeof nextSymbol !== \"string\";\n });\n\n if (expectantStates.length === 0) {\n lines.push('Unexpected ' + tokenDisplay + '. I did not expect any more input. Here is the state of my parse table:\\n');\n this.displayStateStack(lastColumn.states, lines);\n } else {\n lines.push('Unexpected ' + tokenDisplay + '. Instead, I was expecting to see one of the following:\\n');\n // Display a \"state stack\" for each expectant state\n // - which shows you how this state came to be, step by step.\n // If there is more than one derivation, we only display the first one.\n var stateStacks = expectantStates\n .map(function(state) {\n return this.buildFirstStateStack(state, []) || [state];\n }, this);\n // Display each state that is expecting a terminal symbol next.\n stateStacks.forEach(function(stateStack) {\n var state = stateStack[0];\n var nextSymbol = state.rule.symbols[state.dot];\n var symbolDisplay = this.getSymbolDisplay(nextSymbol);\n lines.push('A ' + symbolDisplay + ' based on:');\n this.displayStateStack(stateStack, lines);\n }, this);\n }\n lines.push(\"\");\n return lines.join(\"\\n\");\n }\n \n Parser.prototype.displayStateStack = function(stateStack, lines) {\n var lastDisplay;\n var sameDisplayCount = 0;\n for (var j = 0; j < stateStack.length; j++) {\n var state = stateStack[j];\n var display = state.rule.toString(state.dot);\n if (display === lastDisplay) {\n sameDisplayCount++;\n } else {\n if (sameDisplayCount > 0) {\n lines.push(' ^ ' + sameDisplayCount + ' more lines identical to this');\n }\n sameDisplayCount = 0;\n lines.push(' ' + display);\n }\n lastDisplay = display;\n }\n };\n\n Parser.prototype.getSymbolDisplay = function(symbol) {\n return getSymbolLongDisplay(symbol);\n };\n\n /*\n Builds a the first state stack. You can think of a state stack as the call stack\n of the recursive-descent parser which the Nearley parse algorithm simulates.\n A state stack is represented as an array of state objects. Within a\n state stack, the first item of the array will be the starting\n state, with each successive item in the array going further back into history.\n\n This function needs to be given a starting state and an empty array representing\n the visited states, and it returns an single state stack.\n\n */\n Parser.prototype.buildFirstStateStack = function(state, visited) {\n if (visited.indexOf(state) !== -1) {\n // Found cycle, return null\n // to eliminate this path from the results, because\n // we don't know how to display it meaningfully\n return null;\n }\n if (state.wantedBy.length === 0) {\n return [state];\n }\n var prevState = state.wantedBy[0];\n var childVisited = [state].concat(visited);\n var childResult = this.buildFirstStateStack(prevState, childVisited);\n if (childResult === null) {\n return null;\n }\n return [state].concat(childResult);\n };\n\n Parser.prototype.save = function() {\n var column = this.table[this.current];\n column.lexerState = this.lexerState;\n return column;\n };\n\n Parser.prototype.restore = function(column) {\n var index = column.index;\n this.current = index;\n this.table[index] = column;\n this.table.splice(index + 1);\n this.lexerState = column.lexerState;\n\n // Incrementally keep track of results\n this.results = this.finish();\n };\n\n // nb. deprecated: use save/restore instead!\n Parser.prototype.rewind = function(index) {\n if (!this.options.keepHistory) {\n throw new Error('set option `keepHistory` to enable rewinding')\n }\n // nb. recall column (table) indicies fall between token indicies.\n // col 0 -- token 0 -- col 1\n this.restore(this.table[index]);\n };\n\n Parser.prototype.finish = function() {\n // Return the possible parsings\n var considerations = [];\n var start = this.grammar.start;\n var column = this.table[this.table.length - 1]\n column.states.forEach(function (t) {\n if (t.rule.name === start\n && t.dot === t.rule.symbols.length\n && t.reference === 0\n && t.data !== Parser.fail) {\n considerations.push(t);\n }\n });\n return considerations.map(function(c) {return c.data; });\n };\n\n function getSymbolLongDisplay(symbol) {\n var type = typeof symbol;\n if (type === \"string\") {\n return symbol;\n } else if (type === \"object\") {\n if (symbol.literal) {\n return JSON.stringify(symbol.literal);\n } else if (symbol instanceof RegExp) {\n return 'character matching ' + symbol;\n } else if (symbol.type) {\n return symbol.type + ' token';\n } else if (symbol.test) {\n return 'token matching ' + String(symbol.test);\n } else {\n throw new Error('Unknown symbol type: ' + symbol);\n }\n }\n }\n\n function getSymbolShortDisplay(symbol) {\n var type = typeof symbol;\n if (type === \"string\") {\n return symbol;\n } else if (type === \"object\") {\n if (symbol.literal) {\n return JSON.stringify(symbol.literal);\n } else if (symbol instanceof RegExp) {\n return symbol.toString();\n } else if (symbol.type) {\n return '%' + symbol.type;\n } else if (symbol.test) {\n return '<' + String(symbol.test) + '>';\n } else {\n throw new Error('Unknown symbol type: ' + symbol);\n }\n }\n }\n\n return {\n Parser: Parser,\n Grammar: Grammar,\n Rule: Rule,\n };\n\n}));\n\n\n//# sourceURL=webpack://dcp/./node_modules/nearley/lib/nearley.js?");
|
|
3171
|
+
|
|
3172
|
+
/***/ }),
|
|
3173
|
+
|
|
3121
3174
|
/***/ "./node_modules/node-polyfill-webpack-plugin/node_modules/buffer/index.js":
|
|
3122
3175
|
/*!********************************************************************************!*\
|
|
3123
3176
|
!*** ./node_modules/node-polyfill-webpack-plugin/node_modules/buffer/index.js ***!
|
|
@@ -3777,6 +3830,26 @@ eval("var inherits = __webpack_require__(/*! inherits */ \"./node_modules/inheri
|
|
|
3777
3830
|
|
|
3778
3831
|
/***/ }),
|
|
3779
3832
|
|
|
3833
|
+
/***/ "./node_modules/socket.io-client/node_modules/debug/src/browser.js":
|
|
3834
|
+
/*!*************************************************************************!*\
|
|
3835
|
+
!*** ./node_modules/socket.io-client/node_modules/debug/src/browser.js ***!
|
|
3836
|
+
\*************************************************************************/
|
|
3837
|
+
/***/ ((module, exports, __webpack_require__) => {
|
|
3838
|
+
|
|
3839
|
+
eval("/* provided dependency */ var process = __webpack_require__(/*! ./node_modules/process/browser.js */ \"./node_modules/process/browser.js\");\n/* eslint-env browser */\n\n/**\n * This is the web browser implementation of `debug()`.\n */\n\nexports.formatArgs = formatArgs;\nexports.save = save;\nexports.load = load;\nexports.useColors = useColors;\nexports.storage = localstorage();\nexports.destroy = (() => {\n\tlet warned = false;\n\n\treturn () => {\n\t\tif (!warned) {\n\t\t\twarned = true;\n\t\t\tconsole.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.');\n\t\t}\n\t};\n})();\n\n/**\n * Colors.\n */\n\nexports.colors = [\n\t'#0000CC',\n\t'#0000FF',\n\t'#0033CC',\n\t'#0033FF',\n\t'#0066CC',\n\t'#0066FF',\n\t'#0099CC',\n\t'#0099FF',\n\t'#00CC00',\n\t'#00CC33',\n\t'#00CC66',\n\t'#00CC99',\n\t'#00CCCC',\n\t'#00CCFF',\n\t'#3300CC',\n\t'#3300FF',\n\t'#3333CC',\n\t'#3333FF',\n\t'#3366CC',\n\t'#3366FF',\n\t'#3399CC',\n\t'#3399FF',\n\t'#33CC00',\n\t'#33CC33',\n\t'#33CC66',\n\t'#33CC99',\n\t'#33CCCC',\n\t'#33CCFF',\n\t'#6600CC',\n\t'#6600FF',\n\t'#6633CC',\n\t'#6633FF',\n\t'#66CC00',\n\t'#66CC33',\n\t'#9900CC',\n\t'#9900FF',\n\t'#9933CC',\n\t'#9933FF',\n\t'#99CC00',\n\t'#99CC33',\n\t'#CC0000',\n\t'#CC0033',\n\t'#CC0066',\n\t'#CC0099',\n\t'#CC00CC',\n\t'#CC00FF',\n\t'#CC3300',\n\t'#CC3333',\n\t'#CC3366',\n\t'#CC3399',\n\t'#CC33CC',\n\t'#CC33FF',\n\t'#CC6600',\n\t'#CC6633',\n\t'#CC9900',\n\t'#CC9933',\n\t'#CCCC00',\n\t'#CCCC33',\n\t'#FF0000',\n\t'#FF0033',\n\t'#FF0066',\n\t'#FF0099',\n\t'#FF00CC',\n\t'#FF00FF',\n\t'#FF3300',\n\t'#FF3333',\n\t'#FF3366',\n\t'#FF3399',\n\t'#FF33CC',\n\t'#FF33FF',\n\t'#FF6600',\n\t'#FF6633',\n\t'#FF9900',\n\t'#FF9933',\n\t'#FFCC00',\n\t'#FFCC33'\n];\n\n/**\n * Currently only WebKit-based Web Inspectors, Firefox >= v31,\n * and the Firebug extension (any Firefox version) are known\n * to support \"%c\" CSS customizations.\n *\n * TODO: add a `localStorage` variable to explicitly enable/disable colors\n */\n\n// eslint-disable-next-line complexity\nfunction useColors() {\n\t// NB: In an Electron preload script, document will be defined but not fully\n\t// initialized. Since we know we're in Chrome, we'll just detect this case\n\t// explicitly\n\tif (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) {\n\t\treturn true;\n\t}\n\n\t// Internet Explorer and Edge do not support colors.\n\tif (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\\/(\\d+)/)) {\n\t\treturn false;\n\t}\n\n\t// Is webkit? http://stackoverflow.com/a/16459606/376773\n\t// document is undefined in react-native: https://github.com/facebook/react-native/pull/1632\n\treturn (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) ||\n\t\t// Is firebug? http://stackoverflow.com/a/398120/376773\n\t\t(typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) ||\n\t\t// Is firefox >= v31?\n\t\t// https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages\n\t\t(typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\\/(\\d+)/) && parseInt(RegExp.$1, 10) >= 31) ||\n\t\t// Double check webkit in userAgent just in case we are in a worker\n\t\t(typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\\/(\\d+)/));\n}\n\n/**\n * Colorize log arguments if enabled.\n *\n * @api public\n */\n\nfunction formatArgs(args) {\n\targs[0] = (this.useColors ? '%c' : '') +\n\t\tthis.namespace +\n\t\t(this.useColors ? ' %c' : ' ') +\n\t\targs[0] +\n\t\t(this.useColors ? '%c ' : ' ') +\n\t\t'+' + module.exports.humanize(this.diff);\n\n\tif (!this.useColors) {\n\t\treturn;\n\t}\n\n\tconst c = 'color: ' + this.color;\n\targs.splice(1, 0, c, 'color: inherit');\n\n\t// The final \"%c\" is somewhat tricky, because there could be other\n\t// arguments passed either before or after the %c, so we need to\n\t// figure out the correct index to insert the CSS into\n\tlet index = 0;\n\tlet lastC = 0;\n\targs[0].replace(/%[a-zA-Z%]/g, match => {\n\t\tif (match === '%%') {\n\t\t\treturn;\n\t\t}\n\t\tindex++;\n\t\tif (match === '%c') {\n\t\t\t// We only are interested in the *last* %c\n\t\t\t// (the user may have provided their own)\n\t\t\tlastC = index;\n\t\t}\n\t});\n\n\targs.splice(lastC, 0, c);\n}\n\n/**\n * Invokes `console.debug()` when available.\n * No-op when `console.debug` is not a \"function\".\n * If `console.debug` is not available, falls back\n * to `console.log`.\n *\n * @api public\n */\nexports.log = console.debug || console.log || (() => {});\n\n/**\n * Save `namespaces`.\n *\n * @param {String} namespaces\n * @api private\n */\nfunction save(namespaces) {\n\ttry {\n\t\tif (namespaces) {\n\t\t\texports.storage.setItem('debug', namespaces);\n\t\t} else {\n\t\t\texports.storage.removeItem('debug');\n\t\t}\n\t} catch (error) {\n\t\t// Swallow\n\t\t// XXX (@Qix-) should we be logging these?\n\t}\n}\n\n/**\n * Load `namespaces`.\n *\n * @return {String} returns the previously persisted debug modes\n * @api private\n */\nfunction load() {\n\tlet r;\n\ttry {\n\t\tr = exports.storage.getItem('debug');\n\t} catch (error) {\n\t\t// Swallow\n\t\t// XXX (@Qix-) should we be logging these?\n\t}\n\n\t// If debug isn't set in LS, and we're in Electron, try to load $DEBUG\n\tif (!r && typeof process !== 'undefined' && 'env' in process) {\n\t\tr = process.env.DEBUG;\n\t}\n\n\treturn r;\n}\n\n/**\n * Localstorage attempts to return the localstorage.\n *\n * This is necessary because safari throws\n * when a user disables cookies/localstorage\n * and you attempt to access it.\n *\n * @return {LocalStorage}\n * @api private\n */\n\nfunction localstorage() {\n\ttry {\n\t\t// TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context\n\t\t// The Browser also has localStorage in the global context.\n\t\treturn localStorage;\n\t} catch (error) {\n\t\t// Swallow\n\t\t// XXX (@Qix-) should we be logging these?\n\t}\n}\n\nmodule.exports = __webpack_require__(/*! ./common */ \"./node_modules/socket.io-client/node_modules/debug/src/common.js\")(exports);\n\nconst {formatters} = module.exports;\n\n/**\n * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.\n */\n\nformatters.j = function (v) {\n\ttry {\n\t\treturn JSON.stringify(v);\n\t} catch (error) {\n\t\treturn '[UnexpectedJSONParseError]: ' + error.message;\n\t}\n};\n\n\n//# sourceURL=webpack://dcp/./node_modules/socket.io-client/node_modules/debug/src/browser.js?");
|
|
3840
|
+
|
|
3841
|
+
/***/ }),
|
|
3842
|
+
|
|
3843
|
+
/***/ "./node_modules/socket.io-client/node_modules/debug/src/common.js":
|
|
3844
|
+
/*!************************************************************************!*\
|
|
3845
|
+
!*** ./node_modules/socket.io-client/node_modules/debug/src/common.js ***!
|
|
3846
|
+
\************************************************************************/
|
|
3847
|
+
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
3848
|
+
|
|
3849
|
+
eval("\n/**\n * This is the common logic for both the Node.js and web browser\n * implementations of `debug()`.\n */\n\nfunction setup(env) {\n\tcreateDebug.debug = createDebug;\n\tcreateDebug.default = createDebug;\n\tcreateDebug.coerce = coerce;\n\tcreateDebug.disable = disable;\n\tcreateDebug.enable = enable;\n\tcreateDebug.enabled = enabled;\n\tcreateDebug.humanize = __webpack_require__(/*! ms */ \"./node_modules/ms/index.js\");\n\tcreateDebug.destroy = destroy;\n\n\tObject.keys(env).forEach(key => {\n\t\tcreateDebug[key] = env[key];\n\t});\n\n\t/**\n\t* The currently active debug mode names, and names to skip.\n\t*/\n\n\tcreateDebug.names = [];\n\tcreateDebug.skips = [];\n\n\t/**\n\t* Map of special \"%n\" handling functions, for the debug \"format\" argument.\n\t*\n\t* Valid key names are a single, lower or upper-case letter, i.e. \"n\" and \"N\".\n\t*/\n\tcreateDebug.formatters = {};\n\n\t/**\n\t* Selects a color for a debug namespace\n\t* @param {String} namespace The namespace string for the debug instance to be colored\n\t* @return {Number|String} An ANSI color code for the given namespace\n\t* @api private\n\t*/\n\tfunction selectColor(namespace) {\n\t\tlet hash = 0;\n\n\t\tfor (let i = 0; i < namespace.length; i++) {\n\t\t\thash = ((hash << 5) - hash) + namespace.charCodeAt(i);\n\t\t\thash |= 0; // Convert to 32bit integer\n\t\t}\n\n\t\treturn createDebug.colors[Math.abs(hash) % createDebug.colors.length];\n\t}\n\tcreateDebug.selectColor = selectColor;\n\n\t/**\n\t* Create a debugger with the given `namespace`.\n\t*\n\t* @param {String} namespace\n\t* @return {Function}\n\t* @api public\n\t*/\n\tfunction createDebug(namespace) {\n\t\tlet prevTime;\n\t\tlet enableOverride = null;\n\t\tlet namespacesCache;\n\t\tlet enabledCache;\n\n\t\tfunction debug(...args) {\n\t\t\t// Disabled?\n\t\t\tif (!debug.enabled) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst self = debug;\n\n\t\t\t// Set `diff` timestamp\n\t\t\tconst curr = Number(new Date());\n\t\t\tconst ms = curr - (prevTime || curr);\n\t\t\tself.diff = ms;\n\t\t\tself.prev = prevTime;\n\t\t\tself.curr = curr;\n\t\t\tprevTime = curr;\n\n\t\t\targs[0] = createDebug.coerce(args[0]);\n\n\t\t\tif (typeof args[0] !== 'string') {\n\t\t\t\t// Anything else let's inspect with %O\n\t\t\t\targs.unshift('%O');\n\t\t\t}\n\n\t\t\t// Apply any `formatters` transformations\n\t\t\tlet index = 0;\n\t\t\targs[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => {\n\t\t\t\t// If we encounter an escaped % then don't increase the array index\n\t\t\t\tif (match === '%%') {\n\t\t\t\t\treturn '%';\n\t\t\t\t}\n\t\t\t\tindex++;\n\t\t\t\tconst formatter = createDebug.formatters[format];\n\t\t\t\tif (typeof formatter === 'function') {\n\t\t\t\t\tconst val = args[index];\n\t\t\t\t\tmatch = formatter.call(self, val);\n\n\t\t\t\t\t// Now we need to remove `args[index]` since it's inlined in the `format`\n\t\t\t\t\targs.splice(index, 1);\n\t\t\t\t\tindex--;\n\t\t\t\t}\n\t\t\t\treturn match;\n\t\t\t});\n\n\t\t\t// Apply env-specific formatting (colors, etc.)\n\t\t\tcreateDebug.formatArgs.call(self, args);\n\n\t\t\tconst logFn = self.log || createDebug.log;\n\t\t\tlogFn.apply(self, args);\n\t\t}\n\n\t\tdebug.namespace = namespace;\n\t\tdebug.useColors = createDebug.useColors();\n\t\tdebug.color = createDebug.selectColor(namespace);\n\t\tdebug.extend = extend;\n\t\tdebug.destroy = createDebug.destroy; // XXX Temporary. Will be removed in the next major release.\n\n\t\tObject.defineProperty(debug, 'enabled', {\n\t\t\tenumerable: true,\n\t\t\tconfigurable: false,\n\t\t\tget: () => {\n\t\t\t\tif (enableOverride !== null) {\n\t\t\t\t\treturn enableOverride;\n\t\t\t\t}\n\t\t\t\tif (namespacesCache !== createDebug.namespaces) {\n\t\t\t\t\tnamespacesCache = createDebug.namespaces;\n\t\t\t\t\tenabledCache = createDebug.enabled(namespace);\n\t\t\t\t}\n\n\t\t\t\treturn enabledCache;\n\t\t\t},\n\t\t\tset: v => {\n\t\t\t\tenableOverride = v;\n\t\t\t}\n\t\t});\n\n\t\t// Env-specific initialization logic for debug instances\n\t\tif (typeof createDebug.init === 'function') {\n\t\t\tcreateDebug.init(debug);\n\t\t}\n\n\t\treturn debug;\n\t}\n\n\tfunction extend(namespace, delimiter) {\n\t\tconst newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace);\n\t\tnewDebug.log = this.log;\n\t\treturn newDebug;\n\t}\n\n\t/**\n\t* Enables a debug mode by namespaces. This can include modes\n\t* separated by a colon and wildcards.\n\t*\n\t* @param {String} namespaces\n\t* @api public\n\t*/\n\tfunction enable(namespaces) {\n\t\tcreateDebug.save(namespaces);\n\t\tcreateDebug.namespaces = namespaces;\n\n\t\tcreateDebug.names = [];\n\t\tcreateDebug.skips = [];\n\n\t\tlet i;\n\t\tconst split = (typeof namespaces === 'string' ? namespaces : '').split(/[\\s,]+/);\n\t\tconst len = split.length;\n\n\t\tfor (i = 0; i < len; i++) {\n\t\t\tif (!split[i]) {\n\t\t\t\t// ignore empty strings\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tnamespaces = split[i].replace(/\\*/g, '.*?');\n\n\t\t\tif (namespaces[0] === '-') {\n\t\t\t\tcreateDebug.skips.push(new RegExp('^' + namespaces.slice(1) + '$'));\n\t\t\t} else {\n\t\t\t\tcreateDebug.names.push(new RegExp('^' + namespaces + '$'));\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t* Disable debug output.\n\t*\n\t* @return {String} namespaces\n\t* @api public\n\t*/\n\tfunction disable() {\n\t\tconst namespaces = [\n\t\t\t...createDebug.names.map(toNamespace),\n\t\t\t...createDebug.skips.map(toNamespace).map(namespace => '-' + namespace)\n\t\t].join(',');\n\t\tcreateDebug.enable('');\n\t\treturn namespaces;\n\t}\n\n\t/**\n\t* Returns true if the given mode name is enabled, false otherwise.\n\t*\n\t* @param {String} name\n\t* @return {Boolean}\n\t* @api public\n\t*/\n\tfunction enabled(name) {\n\t\tif (name[name.length - 1] === '*') {\n\t\t\treturn true;\n\t\t}\n\n\t\tlet i;\n\t\tlet len;\n\n\t\tfor (i = 0, len = createDebug.skips.length; i < len; i++) {\n\t\t\tif (createDebug.skips[i].test(name)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\tfor (i = 0, len = createDebug.names.length; i < len; i++) {\n\t\t\tif (createDebug.names[i].test(name)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t* Convert regexp to namespace\n\t*\n\t* @param {RegExp} regxep\n\t* @return {String} namespace\n\t* @api private\n\t*/\n\tfunction toNamespace(regexp) {\n\t\treturn regexp.toString()\n\t\t\t.substring(2, regexp.toString().length - 2)\n\t\t\t.replace(/\\.\\*\\?$/, '*');\n\t}\n\n\t/**\n\t* Coerce `val`.\n\t*\n\t* @param {Mixed} val\n\t* @return {Mixed}\n\t* @api private\n\t*/\n\tfunction coerce(val) {\n\t\tif (val instanceof Error) {\n\t\t\treturn val.stack || val.message;\n\t\t}\n\t\treturn val;\n\t}\n\n\t/**\n\t* XXX DO NOT USE. This is a temporary stub function.\n\t* XXX It WILL be removed in the next major release.\n\t*/\n\tfunction destroy() {\n\t\tconsole.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.');\n\t}\n\n\tcreateDebug.enable(createDebug.load());\n\n\treturn createDebug;\n}\n\nmodule.exports = setup;\n\n\n//# sourceURL=webpack://dcp/./node_modules/socket.io-client/node_modules/debug/src/common.js?");
|
|
3850
|
+
|
|
3851
|
+
/***/ }),
|
|
3852
|
+
|
|
3780
3853
|
/***/ "./node_modules/stream-browserify/index.js":
|
|
3781
3854
|
/*!*************************************************!*\
|
|
3782
3855
|
!*** ./node_modules/stream-browserify/index.js ***!
|
|
@@ -3794,7 +3867,7 @@ eval("// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission
|
|
|
3794
3867
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
3795
3868
|
|
|
3796
3869
|
"use strict";
|
|
3797
|
-
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ Modal)\n/* harmony export */ });\n/**\n * A Small Modal Class\n * @module Modal\n */\n/* globals Event dcpConfig */\nclass Modal {\n constructor (title, message, callback = false, exitHandler = false, {\n continueLabel = 'Continue',\n cancelLabel = 'Cancel',\n cancelVisible = true\n } = {}) {\n const modal = document.createElement('div')\n modal.className = 'dcp-modal-container-old day'\n modal.innerHTML = `\n <dialog class=\"dcp-modal-content\">\n <div class=\"dcp-modal-header\">\n <h2>${title}<button type=\"button\" class=\"close\">×</button></h2>\n ${message ? '<p>' + message + '</p>' : ''}\n </div>\n <div class=\"dcp-modal-loading hidden\">\n <div class='loading'></div>\n </div>\n <form onsubmit='return false' method=\"dialog\">\n <div class=\"dcp-modal-body\"></div>\n <div class=\"dcp-modal-footer ${cancelVisible ? '' : 'centered'}\">\n <button type=\"submit\" class=\"continue green-modal-button\">${continueLabel}</button>\n <button type=\"button\" class=\"cancel green-modal-button\">${cancelLabel}</button>\n </div>\n </form>\n </dialog>`\n\n // To give a reference to do developer who wants to override the form submit.\n // May occur if they want to validate the information in the backend\n // without closing the modal prematurely.\n this.form = modal.querySelector('.dcp-modal-content form')\n this.continueButton = modal.querySelector('.dcp-modal-footer button.continue')\n this.cancelButton = modal.querySelector('.dcp-modal-footer button.cancel')\n this.closeButton = modal.querySelector('.dcp-modal-header .close')\n if (!cancelVisible) {\n this.cancelButton.style.display = 'none'\n }\n\n // To remove the event listener, the reference to the original function\n // added is required.\n this.formSubmitHandler = this.continue.bind(this)\n\n modal.addEventListener('keydown', function (event) {\n event.stopPropagation()\n // 27 is the keycode for the escape key.\n if (event.keyCode === 27) this.close()\n }.bind(this))\n\n this.container = modal\n this.callback = callback\n this.exitHandler = exitHandler\n document.body.appendChild(modal)\n }\n\n changeFormSubmitHandler (newFormSubmitHandler) {\n this.formSubmitHandler = newFormSubmitHandler\n }\n\n /**\n * Validates the form values in the modal and calls the modal's callback\n */\n async continue (event) {\n // To further prevent form submission from trying to redirect from the\n // current page.\n if (event instanceof Event) {\n event.preventDefault()\n }\n let fieldsAreValid = true\n let formElements = this.container.querySelectorAll('.dcp-modal-body select, .dcp-modal-body input, .dcp-modal-body textarea')\n\n const formValues = []\n if (typeof formElements.length === 'undefined') formElements = [formElements]\n // Separate into two loops to enable input validation requiring formValues\n // that come after it. e.g. Two password fields matching.\n for (let i = 0; i < formElements.length; i++) {\n switch (formElements[i].type) {\n case 'file':\n formValues.push(formElements[i])\n break\n case 'checkbox':\n formValues.push(formElements[i].checked)\n break\n default:\n formValues.push(formElements[i].value)\n break\n }\n }\n for (let i = 0; i < formElements.length; i++) {\n if (formElements[i].validation) {\n // Optional fields are allowed to be empty but still can't be wrong if not empty.\n if (!(formElements[i].value === '' && !formElements[i].required)) {\n if (typeof formElements[i].validation === 'function') {\n if (!formElements[i].validation(formValues)) {\n fieldsAreValid = false\n formElements[i].classList.add('is-invalid')\n }\n } else if (!formElements[i].validation.test(formElements[i].value)) {\n fieldsAreValid = false\n formElements[i].classList.add('is-invalid')\n }\n }\n }\n }\n\n if (!fieldsAreValid) return\n\n this.loading()\n if (typeof this.callback === 'function') {\n try {\n return this.callback(formValues)\n } catch (error) {\n console.error('Unexpected error in modal.continue:', error);\n return this.close(false)\n }\n }\n this.close(true)\n }\n\n loading () {\n this.container.querySelector('.dcp-modal-loading').classList.remove('hidden')\n this.container.querySelector('.dcp-modal-body').classList.add('hidden')\n this.container.querySelector('.dcp-modal-footer').classList.add('hidden')\n }\n\n open () {\n this.form.addEventListener('submit', async (event) => {\n const success = await this.formSubmitHandler(event)\n if (success === false) {\n return\n }\n this.close(true)\n })\n // When the user clicks on <span> (x), close the modal\n this.closeButton.addEventListener('click', this.close.bind(this))\n this.cancelButton.addEventListener('click', this.close.bind(this))\n\n // Prevent lingering outlines after clicking some form elements.\n this.container.querySelectorAll('.dcp-modal-body button, .dcp-modal-body input[type=\"checkbox\"]').forEach(element => {\n element.addEventListener('click', () => {\n element.blur()\n })\n })\n\n // Show the modal.\n this.container.style.display = 'block'\n\n const formElements = this.container.querySelectorAll('.dcp-modal-body select, .dcp-modal-body input')\n if (formElements.length) {\n formElements[0].focus()\n if (formElements[0].type === 'text') {\n formElements[0].select()\n }\n for (const el of formElements) {\n if (el.realType) {\n el.type = el.realType\n }\n }\n } else {\n // With no form elements to allow for form submission on enter, focus the\n // continue button.\n this.container.querySelector('.dcp-modal-footer button.continue').focus()\n }\n } // TODO: This should return a promise with the action resolving it\n\n /**\n * Shows the modal and returns a promise of the result of the modal (e.g. was\n * it closed, did its action succeed?)\n */\n showModal () {\n return new Promise((resolve, reject) => {\n this.form.addEventListener('submit', handleContinue.bind(this))\n this.cancelButton.addEventListener('click', handleCancel.bind(this))\n this.closeButton.addEventListener('click', handleCancel.bind(this))\n\n // Prevent lingering outlines after clicking some form elements.\n this.container.querySelectorAll('.dcp-modal-body button, .dcp-modal-body input[type=\"checkbox\"]').forEach(element => {\n element.addEventListener('click', () => {\n element.blur()\n })\n })\n\n // Show the modal.\n this.container.style.display = 'block'\n\n const formElements = this.container.querySelectorAll('.dcp-modal-body select, .dcp-modal-body input')\n if (formElements.length) {\n formElements[0].focus()\n if (formElements[0].type === 'text') {\n formElements[0].select()\n }\n for (const el of formElements) {\n if (el.realType) {\n el.type = el.realType\n }\n }\n } else {\n // With no form elements to allow for form submission on enter, focus the\n // continue button.\n this.continueButton.focus()\n }\n\n async function handleContinue (event) {\n let result\n try {\n result = await this.formSubmitHandler(event)\n } catch (error) {\n reject(error)\n }\n this.close(true)\n resolve(result)\n }\n\n async function handleCancel () {\n let result\n try {\n result = await this.close()\n } catch (error) {\n reject(error)\n }\n resolve(result)\n }\n })\n }\n\n close (success = false) {\n this.container.style.display = 'none'\n if (this.container.parentNode) {\n this.container.parentNode.removeChild(this.container)\n }\n\n // @todo this needs to remove eventlisteners to prevent memory leaks\n\n if ((success !== true) && typeof this.exitHandler === 'function') {\n return this.exitHandler(this)\n }\n }\n\n /**\n * Adds different form elements to the modal depending on the case.\n *\n * @param {*} elements - The properties of the form elements to add.\n * @returns {HTMLElement} The input form elements.\n */\n addFormElement (...elements) {\n const body = this.container.querySelector('.dcp-modal-body')\n const inputElements = []\n let label\n for (let i = 0; i < elements.length; i++) {\n let row = document.createElement('div')\n row.className = 'row'\n\n let col, input\n switch (elements[i].type) {\n case 'button':\n col = document.createElement('div')\n col.className = 'col-md-12'\n\n input = document.createElement('button')\n input.innerHTML = elements[i].label\n input.type = 'button'\n input.classList.add('green-modal-button')\n if (!elements[i].onclick) {\n throw new Error('A button in the modal body should have an on click event handler.')\n }\n input.addEventListener('click', elements[i].onclick)\n\n col.appendChild(input)\n row.appendChild(col)\n break\n case 'textarea':\n col = document.createElement('div')\n col.className = 'col-md-12'\n\n input = document.createElement('textarea')\n input.className = 'text-input-field form-control'\n if (elements[i].placeholder) input.placeholder = elements[i].placeholder\n\n col.appendChild(input)\n row.appendChild(col)\n break\n case 'text':\n case 'email':\n case 'number':\n case 'password': {\n const inputCol = document.createElement('div')\n\n input = document.createElement('input')\n input.type = elements[i].type\n input.validation = elements[i].validation\n input.autocomplete = elements[i].autocomplete || (elements[i].type === 'password' ? 'off' : 'on')\n input.className = 'text-input-field form-control'\n\n // Adding bootstraps custom feedback styles.\n let invalidFeedback = null\n if (elements[i].invalidFeedback) {\n invalidFeedback = document.createElement('div')\n invalidFeedback.className = 'invalid-feedback'\n invalidFeedback.innerText = elements[i].invalidFeedback\n }\n\n if (elements[i].type === 'password') {\n elements[i].realType = 'password'\n }\n\n if (elements[i].label) {\n const labelCol = document.createElement('div')\n label = document.createElement('label')\n label.innerText = elements[i].label\n const inputId = 'dcp-modal-input-' + this.container.querySelectorAll('input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"]').length\n label.setAttribute('for', inputId)\n input.id = inputId\n labelCol.classList.add('col-md-6', 'label-column')\n labelCol.appendChild(label)\n row.appendChild(labelCol)\n inputCol.className = 'col-md-6'\n } else {\n inputCol.className = 'col-md-12'\n }\n\n inputCol.appendChild(input)\n if (invalidFeedback !== null) {\n inputCol.appendChild(invalidFeedback)\n }\n row.appendChild(inputCol)\n break\n }\n case 'select':\n col = document.createElement('div')\n col.className = 'col-md-4'\n\n label = document.createElement('span')\n label.innerText = elements[i].label\n\n col.appendChild(label)\n row.appendChild(col)\n\n col = document.createElement('div')\n col.className = 'col-md-8'\n\n input = document.createElement('select')\n\n col.appendChild(input)\n row.appendChild(col)\n break\n case 'checkbox': {\n row.classList.add('checkbox-row')\n const checkboxLabelCol = document.createElement('div')\n checkboxLabelCol.classList.add('label-column', 'checkbox-label-column')\n\n label = document.createElement('label')\n label.innerText = elements[i].label\n label.for = 'dcp-checkbox-input-' + this.container.querySelectorAll('input[type=\"checkbox\"]').length\n label.setAttribute('for', label.for)\n label.className = 'checkbox-label'\n\n checkboxLabelCol.appendChild(label)\n\n const checkboxCol = document.createElement('div')\n checkboxCol.classList.add('checkbox-column')\n\n input = document.createElement('input')\n input.type = 'checkbox'\n input.id = label.for\n if (elements[i].checked) {\n input.checked = true\n }\n\n checkboxCol.appendChild(input)\n\n if (elements[i].labelToTheRightOfCheckbox) {\n checkboxCol.classList.add('col-md-5')\n row.appendChild(checkboxCol)\n checkboxLabelCol.classList.add('col-md-7')\n row.appendChild(checkboxLabelCol)\n } else {\n checkboxLabelCol.classList.add('col-md-6')\n checkboxCol.classList.add('col-md-6')\n row.appendChild(checkboxLabelCol)\n row.appendChild(checkboxCol)\n }\n break\n }\n case 'file':\n [input, row] = this.addFileInput(elements[i], input, row)\n break\n case 'label':\n row.classList.add('label-row')\n label = document.createElement('label')\n label.innerText = elements[i].label\n row.appendChild(label)\n break\n }\n\n // Copy other possibly specified element properties:\n const inputPropertyNames = ['title', 'inputmode', 'value', 'minLength', 'maxLength', 'size', 'required', 'pattern', 'min', 'max', 'step', 'placeholder', 'accept', 'multiple', 'id', 'onkeypress', 'oninput', 'for', 'readonly', 'autocomplete']\n for (const propertyName of inputPropertyNames) {\n if (Object.prototype.hasOwnProperty.call(elements[i], propertyName)) {\n if (propertyName === 'for' && !label.hasAttribute(propertyName)) {\n label.setAttribute(propertyName, elements[i][propertyName])\n }\n if (propertyName.startsWith('on')) {\n input.addEventListener(propertyName.slice(2), elements[i][propertyName])\n } else {\n input.setAttribute(propertyName, elements[i][propertyName])\n }\n }\n }\n\n inputElements.push(input)\n body.appendChild(row)\n }\n\n if (inputElements.length === 1) return inputElements[0]\n else return inputElements\n }\n\n /**\n * Adds a drag and drop file form element to the modal.\n *\n * @param {*} fileInputProperties - An object specifying some of the\n * properties of the file input element.\n * @param {*} fileInput - Placeholders to help create the file\n * input.\n * @param {HTMLDivElement} row - Placeholders to help create the file\n * input.\n */\n addFileInput (fileInputProperties, fileInput, row) {\n // Adding the upload label.\n const uploadLabel = document.createElement('label')\n uploadLabel.innerText = fileInputProperties.label\n row.appendChild(uploadLabel)\n const body = this.container.querySelector('.dcp-modal-body')\n body.appendChild(row)\n const fileSelectionRow = document.createElement('div')\n fileSelectionRow.id = 'file-selection-row'\n\n // Adding the drag and drop file upload input.\n const dropContainer = document.createElement('div')\n dropContainer.id = 'drop-container'\n\n // Adding an image of a wallet\n const imageContainer = document.createElement('div')\n imageContainer.id = 'image-container'\n const walletImage = document.createElement('span')\n walletImage.classList.add('fas', 'fa-wallet')\n imageContainer.appendChild(walletImage)\n\n // Adding some text prompts\n const dropMessage = document.createElement('span')\n dropMessage.innerText = 'Drop a keystore file here'\n const orMessage = document.createElement('span')\n orMessage.innerText = 'or'\n\n // Adding the manual file input element (hiding the default one)\n const fileInputContainer = document.createElement('div')\n const fileInputLabel = document.createElement('label')\n // Linking the label to the file input so that clicking on the label\n // activates the file input.\n fileInputLabel.setAttribute('for', 'file-input')\n fileInputLabel.innerText = 'Browse'\n fileInput = document.createElement('input')\n fileInput.type = fileInputProperties.type\n fileInput.id = 'file-input'\n // To remove the lingering outline after selecting the file.\n fileInput.addEventListener('click', () => {\n fileInput.blur()\n })\n fileInputContainer.append(fileInput, fileInputLabel)\n\n // Creating the final row element to append to the modal body.\n dropContainer.append(imageContainer, dropMessage, orMessage, fileInputContainer)\n fileSelectionRow.appendChild(dropContainer)\n\n // Adding functionality to the drag and drop file input.\n dropContainer.addEventListener('drop', selectDroppedFile.bind(this))\n dropContainer.addEventListener('drop', unhighlightDropArea)\n // Prevent file from being opened by the browser.\n dropContainer.ondragover = highlightDropArea\n dropContainer.ondragenter = highlightDropArea\n dropContainer.ondragleave = unhighlightDropArea\n\n fileInput.addEventListener('change', handleFileChange)\n\n const fileNamePlaceholder = document.createElement('center')\n fileNamePlaceholder.id = 'file-name-placeholder'\n fileNamePlaceholder.className = 'row'\n fileNamePlaceholder.innerText = ''\n fileSelectionRow.appendChild(fileNamePlaceholder)\n fileNamePlaceholder.classList.add('hidden')\n\n // Check if the continue button is invalid on the keystore upload modal and\n // click it if it should no longer be invalid.\n this.continueButton.addEventListener('invalid', () => {\n const fileFormElements = this.container.querySelectorAll('.dcp-modal-body input[type=\"file\"], .dcp-modal-body input[type=\"text\"]')\n const filledInFileFormElements = Array.from(fileFormElements).filter(fileFormElement => fileFormElement.value !== '')\n if (fileFormElements.length !== 0 && filledInFileFormElements.length !== 0) {\n this.continueButton.setCustomValidity('')\n // Clicking instead of dispatching a submit event to ensure other form validation is used before submitting the form.\n this.continueButton.click()\n }\n })\n\n return [fileInput, fileSelectionRow]\n\n /**\n * Checks that the dropped items contain only a single keystore file.\n * If valid, sets the file input's value to the dropped file.\n * @param {DragEvent} event - Contains the files dropped.\n */\n function selectDroppedFile (event) {\n // Prevent file from being opened.\n event.preventDefault()\n\n // Check if only one file was dropped.\n const wasOneFileDropped = event.dataTransfer.items.length === 1 ||\n event.dataTransfer.files.length === 1\n updateFileSelectionStatus(wasOneFileDropped)\n if (!wasOneFileDropped) {\n fileInput.setCustomValidity('Only one file can be uploaded.')\n fileInput.reportValidity()\n return\n } else {\n fileInput.setCustomValidity('')\n }\n\n // Now to use the DataTransfer interface to access the file(s), setting\n // the value of the file input.\n const file = event.dataTransfer.files[0]\n\n if (checkFileExtension(file)) {\n fileInput.files = event.dataTransfer.files\n fileInput.dispatchEvent(new Event('change'))\n }\n }\n\n function handleFileChange () {\n if (checkFileExtension(this.files[0]) && this.files.length === 1) {\n fileNamePlaceholder.innerText = `Selected File: ${this.files[0].name}`\n updateFileSelectionStatus(true)\n // Invoke a callback if additional functionality is required.\n if (typeof fileInputProperties.callback === 'function') {\n fileInputProperties.callback(this.files[0])\n }\n }\n }\n\n /**\n * Checks if the file extension on the inputted file is correct.\n * @param {File} file - The file to check\n * @returns {boolean} True if the file extension is valid, false otherwise.\n */\n function checkFileExtension (file) {\n // If there's no restriction, return true.\n if (!fileInputProperties.extension) {\n return true\n }\n const fileExtension = file.name.split('.').pop()\n const isValidExtension = fileExtension === fileInputProperties.extension\n updateFileSelectionStatus(isValidExtension)\n if (!isValidExtension) {\n fileInput.setCustomValidity(`Only a .${fileInputProperties.extension} file can be uploaded.`)\n fileInput.reportValidity()\n fileNamePlaceholder.classList.add('hidden')\n } else {\n fileInput.setCustomValidity('')\n }\n return isValidExtension\n }\n\n /**\n * Updates the file input to reflect the validity of the current file\n * selection.\n * @param {boolean} isValidFileSelection - True if a single .keystore file\n * was selected. False otherwise.\n */\n function updateFileSelectionStatus (isValidFileSelection) {\n imageContainer.innerHTML = ''\n const statusImage = document.createElement('span')\n statusImage.classList.add('fas', isValidFileSelection ? 'fa-check' : 'fa-times')\n statusImage.style.color = isValidFileSelection ? 'green' : 'red'\n imageContainer.appendChild(statusImage)\n\n if (!isValidFileSelection) {\n fileInput.value = null\n fileNamePlaceholder.classList.add('hidden')\n } else {\n fileNamePlaceholder.classList.remove('hidden')\n }\n\n // If the modal contains a password field for a keystore file, change its\n // visibility.\n const walletPasswordInputContainer = document.querySelector('.dcp-modal-body input[type=\"password\"]').parentElement.parentElement\n if (walletPasswordInputContainer) {\n if (isValidFileSelection) {\n walletPasswordInputContainer.classList.remove('hidden')\n const walletPasswordInput = document.querySelector('.dcp-modal-body input[type=\"password\"]')\n walletPasswordInput.focus()\n } else {\n walletPasswordInputContainer.classList.add('hidden')\n }\n }\n }\n\n function highlightDropArea (event) {\n event.preventDefault()\n this.classList.add('highlight')\n }\n\n function unhighlightDropArea (event) {\n event.preventDefault()\n this.classList.remove('highlight')\n }\n }\n\n /**\n * Sets up a custom tooltip to pop up when the passwords do not match, but are\n * valid otherwise.\n */\n addFormValidationForPasswordConfirmation () {\n const [newPassword, confirmPassword] = document.querySelectorAll('.dcp-modal-body input[type=\"password\"]')\n if (!newPassword || !confirmPassword) {\n throw Error('New Password field and Confirm Password fields not present.')\n }\n\n newPassword.addEventListener('input', checkMatchingPasswords)\n confirmPassword.addEventListener('input', checkMatchingPasswords)\n\n function checkMatchingPasswords () {\n if (newPassword.value !== confirmPassword.value &&\n newPassword.validity.valid &&\n confirmPassword.validity.valid) {\n newPassword.setCustomValidity('Both passwords must match.')\n } else if (newPassword.value === confirmPassword.value ||\n newPassword.validity.tooShort ||\n newPassword.validity.patternMismatch ||\n newPassword.validity.valueMissing ||\n confirmPassword.validity.tooShort ||\n confirmPassword.validity.patternMismatch ||\n confirmPassword.validity.valueMissing) {\n // If the passwords fields match or have become invalidated some other\n // way again, reset the custom message.\n newPassword.setCustomValidity('')\n }\n }\n }\n\n updateInvalidEmailMessage() {\n const email = document.querySelector('.dcp-modal-body input[id=\"email\"')\n if (!email){\n throw Error(\"Email field not present\")\n }\n email.addEventListener('input', checkValidEmail);\n function checkValidEmail() {\n if (!email.validity.patternMismatch &&\n !email.validity.valueMissing) {\n email.setCustomValidity('')\n } else {\n email.setCustomValidity(\"Enter a valid email address.\")\n }\n\n }\n }\n\n /**\n * Adds message(s) to the modal's body.\n * @param {string} messages - The message(s) to add to the modal's body.\n * @returns Paragraph element(s) containing the message(s) added to the\n * modal's body.\n */\n addMessage (...messages) {\n const elements = []\n const body = this.container.querySelector('.dcp-modal-body')\n for (let i = 0; i < messages.length; i++) {\n const row = document.createElement('div')\n row.className = 'row'\n\n const paragraph = document.createElement('p')\n paragraph.innerHTML = messages[i]\n paragraph.classList.add('message')\n row.appendChild(paragraph)\n body.appendChild(row)\n\n elements.push(paragraph)\n }\n\n if (elements.length === 1) return elements[0]\n else return elements\n }\n\n addHorizontalRule () {\n const body = this.container.querySelector('.dcp-modal-body')\n body.appendChild(document.createElement('hr'))\n }\n\n // Does what it says. Still ill advised to use unless you have to.\n addCustomHTML (htmlStr, browseCallback) {\n const elements = []\n const body = this.container.querySelector('.dcp-modal-body')\n body.innerHTML += htmlStr\n body.querySelector('#browse-button').addEventListener('click', browseCallback.bind(this, this))\n\n if (elements.length === 1) return elements[0]\n else return elements\n }\n\n addButton (...buttons) {\n const elements = []\n const body = this.container.querySelector('.dcp-modal-body')\n for (let i = 0; i < buttons.length; i++) {\n const row = document.createElement('div')\n row.className = 'row'\n\n let col = document.createElement('div')\n col.className = 'col-md-4'\n\n const description = document.createElement('span')\n description.innerText = buttons[i].description\n\n col.appendChild(description)\n row.appendChild(col)\n\n col = document.createElement('div')\n col.className = 'col-md-8'\n\n const button = document.createElement('button')\n button.innerText = buttons[i].label\n button.addEventListener('click', buttons[i].callback.bind(this, this))\n\n elements.push(button)\n\n col.appendChild(button)\n row.appendChild(col)\n\n body.appendChild(row)\n }\n\n if (elements.length === 1) return elements[0]\n else return elements\n }\n}\n\n\n// Inject our special stylesheet from dcp-client only if we're on the portal webpage.\nif (typeof window !== 'undefined' && typeof document !== 'undefined' && dcpConfig.portal.location.hostname === window.location.hostname) {\n // <link rel='stylesheet' href='/css/dashboard.css'>\n const stylesheet = document.createElement('link')\n stylesheet.rel = 'stylesheet'\n // Needed for the duplicate check done later.\n stylesheet.id = 'dcp-modal-styles'\n\n const dcpClientBundle = document.getElementById('_dcp_client_bundle')\n let src\n if (dcpClientBundle) {\n src = dcpClientBundle.src.replace('dcp-client-bundle.js', 'dcp-modal-style.css')\n } else {\n src = dcpConfig.portal.location.href + 'dcp-client/dist/dcp-modal-style.css'\n }\n\n stylesheet.href = src\n // If the style was injected before, don't inject it again.\n // Could occur when loading a file that imports Modal.js and loading\n // comput.min.js in the same HTML file.\n if (document.getElementById(stylesheet.id) === null) {\n document.getElementsByTagName('head')[0].appendChild(stylesheet)\n }\n\n if (typeof {\"version\":\"764872ded972b9068efdd89d2b1144bf7cb48e7e\",\"branch\":\"release\",\"dcpClient\":{\"version\":\"4.2.18\",\"from\":\"git+ssh://git@gitlab.com/Distributed-Compute-Protocol/dcp-client.git#prod-20221018\",\"resolved\":\"git+ssh://git@gitlab.com/Distributed-Compute-Protocol/dcp-client.git#c5ea513e7e6bc0294075a59ae63c78815c7a73ee\"},\"built\":\"Fri Oct 21 2022 11:28:26 GMT-0400 (Eastern Daylight Saving Time)\",\"config\":{\"generated\":\"Fri 21 Oct 2022 11:28:24 AM EDT by erose on lorge\",\"build\":\"debug\"},\"webpack\":\"5.74.0\",\"node\":\"v14.20.1\"} !== 'undefined' && typeof window.Modal === 'undefined') {\n window.Modal = Modal\n }\n}\n\n\n//# sourceURL=webpack://dcp/./portal/www/js/modal.js?");
|
|
3870
|
+
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ Modal)\n/* harmony export */ });\n/**\n * A Small Modal Class\n * @module Modal\n */\n/* globals Event dcpConfig */\nclass Modal {\n constructor (title, message, callback = false, exitHandler = false, {\n continueLabel = 'Continue',\n cancelLabel = 'Cancel',\n cancelVisible = true\n } = {}) {\n const modal = document.createElement('div')\n modal.className = 'dcp-modal-container-old day'\n modal.innerHTML = `\n <dialog class=\"dcp-modal-content\">\n <div class=\"dcp-modal-header\">\n <h2>${title}<button type=\"button\" class=\"close\">×</button></h2>\n ${message ? '<p>' + message + '</p>' : ''}\n </div>\n <div class=\"dcp-modal-loading hidden\">\n <div class='loading'></div>\n </div>\n <form onsubmit='return false' method=\"dialog\">\n <div class=\"dcp-modal-body\"></div>\n <div class=\"dcp-modal-footer ${cancelVisible ? '' : 'centered'}\">\n <button type=\"submit\" class=\"continue green-modal-button\">${continueLabel}</button>\n <button type=\"button\" class=\"cancel green-modal-button\">${cancelLabel}</button>\n </div>\n </form>\n </dialog>`\n\n // To give a reference to do developer who wants to override the form submit.\n // May occur if they want to validate the information in the backend\n // without closing the modal prematurely.\n this.form = modal.querySelector('.dcp-modal-content form')\n this.continueButton = modal.querySelector('.dcp-modal-footer button.continue')\n this.cancelButton = modal.querySelector('.dcp-modal-footer button.cancel')\n this.closeButton = modal.querySelector('.dcp-modal-header .close')\n if (!cancelVisible) {\n this.cancelButton.style.display = 'none'\n }\n\n // To remove the event listener, the reference to the original function\n // added is required.\n this.formSubmitHandler = this.continue.bind(this)\n\n modal.addEventListener('keydown', function (event) {\n event.stopPropagation()\n // 27 is the keycode for the escape key.\n if (event.keyCode === 27) this.close()\n }.bind(this))\n\n this.container = modal\n this.callback = callback\n this.exitHandler = exitHandler\n document.body.appendChild(modal)\n }\n\n changeFormSubmitHandler (newFormSubmitHandler) {\n this.formSubmitHandler = newFormSubmitHandler\n }\n\n /**\n * Validates the form values in the modal and calls the modal's callback\n */\n async continue (event) {\n // To further prevent form submission from trying to redirect from the\n // current page.\n if (event instanceof Event) {\n event.preventDefault()\n }\n let fieldsAreValid = true\n let formElements = this.container.querySelectorAll('.dcp-modal-body select, .dcp-modal-body input, .dcp-modal-body textarea')\n\n const formValues = []\n if (typeof formElements.length === 'undefined') formElements = [formElements]\n // Separate into two loops to enable input validation requiring formValues\n // that come after it. e.g. Two password fields matching.\n for (let i = 0; i < formElements.length; i++) {\n switch (formElements[i].type) {\n case 'file':\n formValues.push(formElements[i])\n break\n case 'checkbox':\n formValues.push(formElements[i].checked)\n break\n default:\n formValues.push(formElements[i].value)\n break\n }\n }\n for (let i = 0; i < formElements.length; i++) {\n if (formElements[i].validation) {\n // Optional fields are allowed to be empty but still can't be wrong if not empty.\n if (!(formElements[i].value === '' && !formElements[i].required)) {\n if (typeof formElements[i].validation === 'function') {\n if (!formElements[i].validation(formValues)) {\n fieldsAreValid = false\n formElements[i].classList.add('is-invalid')\n }\n } else if (!formElements[i].validation.test(formElements[i].value)) {\n fieldsAreValid = false\n formElements[i].classList.add('is-invalid')\n }\n }\n }\n }\n\n if (!fieldsAreValid) return\n\n this.loading()\n if (typeof this.callback === 'function') {\n try {\n return this.callback(formValues)\n } catch (error) {\n console.error('Unexpected error in modal.continue:', error);\n return this.close(false)\n }\n }\n this.close(true)\n }\n\n loading () {\n this.container.querySelector('.dcp-modal-loading').classList.remove('hidden')\n this.container.querySelector('.dcp-modal-body').classList.add('hidden')\n this.container.querySelector('.dcp-modal-footer').classList.add('hidden')\n }\n\n open () {\n this.form.addEventListener('submit', async (event) => {\n const success = await this.formSubmitHandler(event)\n if (success === false) {\n return\n }\n this.close(true)\n })\n // When the user clicks on <span> (x), close the modal\n this.closeButton.addEventListener('click', this.close.bind(this))\n this.cancelButton.addEventListener('click', this.close.bind(this))\n\n // Prevent lingering outlines after clicking some form elements.\n this.container.querySelectorAll('.dcp-modal-body button, .dcp-modal-body input[type=\"checkbox\"]').forEach(element => {\n element.addEventListener('click', () => {\n element.blur()\n })\n })\n\n // Show the modal.\n this.container.style.display = 'block'\n\n const formElements = this.container.querySelectorAll('.dcp-modal-body select, .dcp-modal-body input')\n if (formElements.length) {\n formElements[0].focus()\n if (formElements[0].type === 'text') {\n formElements[0].select()\n }\n for (const el of formElements) {\n if (el.realType) {\n el.type = el.realType\n }\n }\n } else {\n // With no form elements to allow for form submission on enter, focus the\n // continue button.\n this.container.querySelector('.dcp-modal-footer button.continue').focus()\n }\n } // TODO: This should return a promise with the action resolving it\n\n /**\n * Shows the modal and returns a promise of the result of the modal (e.g. was\n * it closed, did its action succeed?)\n */\n showModal () {\n return new Promise((resolve, reject) => {\n this.form.addEventListener('submit', handleContinue.bind(this))\n this.cancelButton.addEventListener('click', handleCancel.bind(this))\n this.closeButton.addEventListener('click', handleCancel.bind(this))\n\n // Prevent lingering outlines after clicking some form elements.\n this.container.querySelectorAll('.dcp-modal-body button, .dcp-modal-body input[type=\"checkbox\"]').forEach(element => {\n element.addEventListener('click', () => {\n element.blur()\n })\n })\n\n // Show the modal.\n this.container.style.display = 'block'\n\n const formElements = this.container.querySelectorAll('.dcp-modal-body select, .dcp-modal-body input')\n if (formElements.length) {\n formElements[0].focus()\n if (formElements[0].type === 'text') {\n formElements[0].select()\n }\n for (const el of formElements) {\n if (el.realType) {\n el.type = el.realType\n }\n }\n } else {\n // With no form elements to allow for form submission on enter, focus the\n // continue button.\n this.continueButton.focus()\n }\n\n async function handleContinue (event) {\n let result\n try {\n result = await this.formSubmitHandler(event)\n } catch (error) {\n reject(error)\n }\n this.close(true)\n resolve(result)\n }\n\n async function handleCancel () {\n let result\n try {\n result = await this.close()\n } catch (error) {\n reject(error)\n }\n resolve(result)\n }\n })\n }\n\n close (success = false) {\n this.container.style.display = 'none'\n if (this.container.parentNode) {\n this.container.parentNode.removeChild(this.container)\n }\n\n // @todo this needs to remove eventlisteners to prevent memory leaks\n\n if ((success !== true) && typeof this.exitHandler === 'function') {\n return this.exitHandler(this)\n }\n }\n\n /**\n * Adds different form elements to the modal depending on the case.\n *\n * @param {*} elements - The properties of the form elements to add.\n * @returns {HTMLElement} The input form elements.\n */\n addFormElement (...elements) {\n const body = this.container.querySelector('.dcp-modal-body')\n const inputElements = []\n let label\n for (let i = 0; i < elements.length; i++) {\n let row = document.createElement('div')\n row.className = 'row'\n\n let col, input\n switch (elements[i].type) {\n case 'button':\n col = document.createElement('div')\n col.className = 'col-md-12'\n\n input = document.createElement('button')\n input.innerHTML = elements[i].label\n input.type = 'button'\n input.classList.add('green-modal-button')\n if (!elements[i].onclick) {\n throw new Error('A button in the modal body should have an on click event handler.')\n }\n input.addEventListener('click', elements[i].onclick)\n\n col.appendChild(input)\n row.appendChild(col)\n break\n case 'textarea':\n col = document.createElement('div')\n col.className = 'col-md-12'\n\n input = document.createElement('textarea')\n input.className = 'text-input-field form-control'\n if (elements[i].placeholder) input.placeholder = elements[i].placeholder\n\n col.appendChild(input)\n row.appendChild(col)\n break\n case 'text':\n case 'email':\n case 'number':\n case 'password': {\n const inputCol = document.createElement('div')\n\n input = document.createElement('input')\n input.type = elements[i].type\n input.validation = elements[i].validation\n input.autocomplete = elements[i].autocomplete || (elements[i].type === 'password' ? 'off' : 'on')\n input.className = 'text-input-field form-control'\n\n // Adding bootstraps custom feedback styles.\n let invalidFeedback = null\n if (elements[i].invalidFeedback) {\n invalidFeedback = document.createElement('div')\n invalidFeedback.className = 'invalid-feedback'\n invalidFeedback.innerText = elements[i].invalidFeedback\n }\n\n if (elements[i].type === 'password') {\n elements[i].realType = 'password'\n }\n\n if (elements[i].label) {\n const labelCol = document.createElement('div')\n label = document.createElement('label')\n label.innerText = elements[i].label\n const inputId = 'dcp-modal-input-' + this.container.querySelectorAll('input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"]').length\n label.setAttribute('for', inputId)\n input.id = inputId\n labelCol.classList.add('col-md-6', 'label-column')\n labelCol.appendChild(label)\n row.appendChild(labelCol)\n inputCol.className = 'col-md-6'\n } else {\n inputCol.className = 'col-md-12'\n }\n\n inputCol.appendChild(input)\n if (invalidFeedback !== null) {\n inputCol.appendChild(invalidFeedback)\n }\n row.appendChild(inputCol)\n break\n }\n case 'select':\n col = document.createElement('div')\n col.className = 'col-md-4'\n\n label = document.createElement('span')\n label.innerText = elements[i].label\n\n col.appendChild(label)\n row.appendChild(col)\n\n col = document.createElement('div')\n col.className = 'col-md-8'\n\n input = document.createElement('select')\n\n col.appendChild(input)\n row.appendChild(col)\n break\n case 'checkbox': {\n row.classList.add('checkbox-row')\n const checkboxLabelCol = document.createElement('div')\n checkboxLabelCol.classList.add('label-column', 'checkbox-label-column')\n\n label = document.createElement('label')\n label.innerText = elements[i].label\n label.for = 'dcp-checkbox-input-' + this.container.querySelectorAll('input[type=\"checkbox\"]').length\n label.setAttribute('for', label.for)\n label.className = 'checkbox-label'\n\n checkboxLabelCol.appendChild(label)\n\n const checkboxCol = document.createElement('div')\n checkboxCol.classList.add('checkbox-column')\n\n input = document.createElement('input')\n input.type = 'checkbox'\n input.id = label.for\n if (elements[i].checked) {\n input.checked = true\n }\n\n checkboxCol.appendChild(input)\n\n if (elements[i].labelToTheRightOfCheckbox) {\n checkboxCol.classList.add('col-md-5')\n row.appendChild(checkboxCol)\n checkboxLabelCol.classList.add('col-md-7')\n row.appendChild(checkboxLabelCol)\n } else {\n checkboxLabelCol.classList.add('col-md-6')\n checkboxCol.classList.add('col-md-6')\n row.appendChild(checkboxLabelCol)\n row.appendChild(checkboxCol)\n }\n break\n }\n case 'file':\n [input, row] = this.addFileInput(elements[i], input, row)\n break\n case 'label':\n row.classList.add('label-row')\n label = document.createElement('label')\n label.innerText = elements[i].label\n row.appendChild(label)\n break\n }\n\n // Copy other possibly specified element properties:\n const inputPropertyNames = ['title', 'inputmode', 'value', 'minLength', 'maxLength', 'size', 'required', 'pattern', 'min', 'max', 'step', 'placeholder', 'accept', 'multiple', 'id', 'onkeypress', 'oninput', 'for', 'readonly', 'autocomplete']\n for (const propertyName of inputPropertyNames) {\n if (Object.prototype.hasOwnProperty.call(elements[i], propertyName)) {\n if (propertyName === 'for' && !label.hasAttribute(propertyName)) {\n label.setAttribute(propertyName, elements[i][propertyName])\n }\n if (propertyName.startsWith('on')) {\n input.addEventListener(propertyName.slice(2), elements[i][propertyName])\n } else {\n input.setAttribute(propertyName, elements[i][propertyName])\n }\n }\n }\n\n inputElements.push(input)\n body.appendChild(row)\n }\n\n if (inputElements.length === 1) return inputElements[0]\n else return inputElements\n }\n\n /**\n * Adds a drag and drop file form element to the modal.\n *\n * @param {*} fileInputProperties - An object specifying some of the\n * properties of the file input element.\n * @param {*} fileInput - Placeholders to help create the file\n * input.\n * @param {HTMLDivElement} row - Placeholders to help create the file\n * input.\n */\n addFileInput (fileInputProperties, fileInput, row) {\n // Adding the upload label.\n const uploadLabel = document.createElement('label')\n uploadLabel.innerText = fileInputProperties.label\n row.appendChild(uploadLabel)\n const body = this.container.querySelector('.dcp-modal-body')\n body.appendChild(row)\n const fileSelectionRow = document.createElement('div')\n fileSelectionRow.id = 'file-selection-row'\n\n // Adding the drag and drop file upload input.\n const dropContainer = document.createElement('div')\n dropContainer.id = 'drop-container'\n\n // Adding an image of a wallet\n const imageContainer = document.createElement('div')\n imageContainer.id = 'image-container'\n const walletImage = document.createElement('span')\n walletImage.classList.add('fas', 'fa-wallet')\n imageContainer.appendChild(walletImage)\n\n // Adding some text prompts\n const dropMessage = document.createElement('span')\n dropMessage.innerText = 'Drop a keystore file here'\n const orMessage = document.createElement('span')\n orMessage.innerText = 'or'\n\n // Adding the manual file input element (hiding the default one)\n const fileInputContainer = document.createElement('div')\n const fileInputLabel = document.createElement('label')\n // Linking the label to the file input so that clicking on the label\n // activates the file input.\n fileInputLabel.setAttribute('for', 'file-input')\n fileInputLabel.innerText = 'Browse'\n fileInput = document.createElement('input')\n fileInput.type = fileInputProperties.type\n fileInput.id = 'file-input'\n // To remove the lingering outline after selecting the file.\n fileInput.addEventListener('click', () => {\n fileInput.blur()\n })\n fileInputContainer.append(fileInput, fileInputLabel)\n\n // Creating the final row element to append to the modal body.\n dropContainer.append(imageContainer, dropMessage, orMessage, fileInputContainer)\n fileSelectionRow.appendChild(dropContainer)\n\n // Adding functionality to the drag and drop file input.\n dropContainer.addEventListener('drop', selectDroppedFile.bind(this))\n dropContainer.addEventListener('drop', unhighlightDropArea)\n // Prevent file from being opened by the browser.\n dropContainer.ondragover = highlightDropArea\n dropContainer.ondragenter = highlightDropArea\n dropContainer.ondragleave = unhighlightDropArea\n\n fileInput.addEventListener('change', handleFileChange)\n\n const fileNamePlaceholder = document.createElement('center')\n fileNamePlaceholder.id = 'file-name-placeholder'\n fileNamePlaceholder.className = 'row'\n fileNamePlaceholder.innerText = ''\n fileSelectionRow.appendChild(fileNamePlaceholder)\n fileNamePlaceholder.classList.add('hidden')\n\n // Check if the continue button is invalid on the keystore upload modal and\n // click it if it should no longer be invalid.\n this.continueButton.addEventListener('invalid', () => {\n const fileFormElements = this.container.querySelectorAll('.dcp-modal-body input[type=\"file\"], .dcp-modal-body input[type=\"text\"]')\n const filledInFileFormElements = Array.from(fileFormElements).filter(fileFormElement => fileFormElement.value !== '')\n if (fileFormElements.length !== 0 && filledInFileFormElements.length !== 0) {\n this.continueButton.setCustomValidity('')\n // Clicking instead of dispatching a submit event to ensure other form validation is used before submitting the form.\n this.continueButton.click()\n }\n })\n\n return [fileInput, fileSelectionRow]\n\n /**\n * Checks that the dropped items contain only a single keystore file.\n * If valid, sets the file input's value to the dropped file.\n * @param {DragEvent} event - Contains the files dropped.\n */\n function selectDroppedFile (event) {\n // Prevent file from being opened.\n event.preventDefault()\n\n // Check if only one file was dropped.\n const wasOneFileDropped = event.dataTransfer.items.length === 1 ||\n event.dataTransfer.files.length === 1\n updateFileSelectionStatus(wasOneFileDropped)\n if (!wasOneFileDropped) {\n fileInput.setCustomValidity('Only one file can be uploaded.')\n fileInput.reportValidity()\n return\n } else {\n fileInput.setCustomValidity('')\n }\n\n // Now to use the DataTransfer interface to access the file(s), setting\n // the value of the file input.\n const file = event.dataTransfer.files[0]\n\n if (checkFileExtension(file)) {\n fileInput.files = event.dataTransfer.files\n fileInput.dispatchEvent(new Event('change'))\n }\n }\n\n function handleFileChange () {\n if (checkFileExtension(this.files[0]) && this.files.length === 1) {\n fileNamePlaceholder.innerText = `Selected File: ${this.files[0].name}`\n updateFileSelectionStatus(true)\n // Invoke a callback if additional functionality is required.\n if (typeof fileInputProperties.callback === 'function') {\n fileInputProperties.callback(this.files[0])\n }\n }\n }\n\n /**\n * Checks if the file extension on the inputted file is correct.\n * @param {File} file - The file to check\n * @returns {boolean} True if the file extension is valid, false otherwise.\n */\n function checkFileExtension (file) {\n // If there's no restriction, return true.\n if (!fileInputProperties.extension) {\n return true\n }\n const fileExtension = file.name.split('.').pop()\n const isValidExtension = fileExtension === fileInputProperties.extension\n updateFileSelectionStatus(isValidExtension)\n if (!isValidExtension) {\n fileInput.setCustomValidity(`Only a .${fileInputProperties.extension} file can be uploaded.`)\n fileInput.reportValidity()\n fileNamePlaceholder.classList.add('hidden')\n } else {\n fileInput.setCustomValidity('')\n }\n return isValidExtension\n }\n\n /**\n * Updates the file input to reflect the validity of the current file\n * selection.\n * @param {boolean} isValidFileSelection - True if a single .keystore file\n * was selected. False otherwise.\n */\n function updateFileSelectionStatus (isValidFileSelection) {\n imageContainer.innerHTML = ''\n const statusImage = document.createElement('span')\n statusImage.classList.add('fas', isValidFileSelection ? 'fa-check' : 'fa-times')\n statusImage.style.color = isValidFileSelection ? 'green' : 'red'\n imageContainer.appendChild(statusImage)\n\n if (!isValidFileSelection) {\n fileInput.value = null\n fileNamePlaceholder.classList.add('hidden')\n } else {\n fileNamePlaceholder.classList.remove('hidden')\n }\n\n // If the modal contains a password field for a keystore file, change its\n // visibility.\n const walletPasswordInputContainer = document.querySelector('.dcp-modal-body input[type=\"password\"]').parentElement.parentElement\n if (walletPasswordInputContainer) {\n if (isValidFileSelection) {\n walletPasswordInputContainer.classList.remove('hidden')\n const walletPasswordInput = document.querySelector('.dcp-modal-body input[type=\"password\"]')\n walletPasswordInput.focus()\n } else {\n walletPasswordInputContainer.classList.add('hidden')\n }\n }\n }\n\n function highlightDropArea (event) {\n event.preventDefault()\n this.classList.add('highlight')\n }\n\n function unhighlightDropArea (event) {\n event.preventDefault()\n this.classList.remove('highlight')\n }\n }\n\n /**\n * Sets up a custom tooltip to pop up when the passwords do not match, but are\n * valid otherwise.\n */\n addFormValidationForPasswordConfirmation () {\n const [newPassword, confirmPassword] = document.querySelectorAll('.dcp-modal-body input[type=\"password\"]')\n if (!newPassword || !confirmPassword) {\n throw Error('New Password field and Confirm Password fields not present.')\n }\n\n newPassword.addEventListener('input', checkMatchingPasswords)\n confirmPassword.addEventListener('input', checkMatchingPasswords)\n\n function checkMatchingPasswords () {\n if (newPassword.value !== confirmPassword.value &&\n newPassword.validity.valid &&\n confirmPassword.validity.valid) {\n newPassword.setCustomValidity('Both passwords must match.')\n } else if (newPassword.value === confirmPassword.value ||\n newPassword.validity.tooShort ||\n newPassword.validity.patternMismatch ||\n newPassword.validity.valueMissing ||\n confirmPassword.validity.tooShort ||\n confirmPassword.validity.patternMismatch ||\n confirmPassword.validity.valueMissing) {\n // If the passwords fields match or have become invalidated some other\n // way again, reset the custom message.\n newPassword.setCustomValidity('')\n }\n }\n }\n\n updateInvalidEmailMessage() {\n const email = document.querySelector('.dcp-modal-body input[id=\"email\"')\n if (!email){\n throw Error(\"Email field not present\")\n }\n email.addEventListener('input', checkValidEmail);\n function checkValidEmail() {\n if (!email.validity.patternMismatch &&\n !email.validity.valueMissing) {\n email.setCustomValidity('')\n } else {\n email.setCustomValidity(\"Enter a valid email address.\")\n }\n\n }\n }\n\n /**\n * Adds message(s) to the modal's body.\n * @param {string} messages - The message(s) to add to the modal's body.\n * @returns Paragraph element(s) containing the message(s) added to the\n * modal's body.\n */\n addMessage (...messages) {\n const elements = []\n const body = this.container.querySelector('.dcp-modal-body')\n for (let i = 0; i < messages.length; i++) {\n const row = document.createElement('div')\n row.className = 'row'\n\n const paragraph = document.createElement('p')\n paragraph.innerHTML = messages[i]\n paragraph.classList.add('message')\n row.appendChild(paragraph)\n body.appendChild(row)\n\n elements.push(paragraph)\n }\n\n if (elements.length === 1) return elements[0]\n else return elements\n }\n\n addHorizontalRule () {\n const body = this.container.querySelector('.dcp-modal-body')\n body.appendChild(document.createElement('hr'))\n }\n\n // Does what it says. Still ill advised to use unless you have to.\n addCustomHTML (htmlStr, browseCallback) {\n const elements = []\n const body = this.container.querySelector('.dcp-modal-body')\n body.innerHTML += htmlStr\n body.querySelector('#browse-button').addEventListener('click', browseCallback.bind(this, this))\n\n if (elements.length === 1) return elements[0]\n else return elements\n }\n\n addButton (...buttons) {\n const elements = []\n const body = this.container.querySelector('.dcp-modal-body')\n for (let i = 0; i < buttons.length; i++) {\n const row = document.createElement('div')\n row.className = 'row'\n\n let col = document.createElement('div')\n col.className = 'col-md-4'\n\n const description = document.createElement('span')\n description.innerText = buttons[i].description\n\n col.appendChild(description)\n row.appendChild(col)\n\n col = document.createElement('div')\n col.className = 'col-md-8'\n\n const button = document.createElement('button')\n button.innerText = buttons[i].label\n button.addEventListener('click', buttons[i].callback.bind(this, this))\n\n elements.push(button)\n\n col.appendChild(button)\n row.appendChild(col)\n\n body.appendChild(row)\n }\n\n if (elements.length === 1) return elements[0]\n else return elements\n }\n}\n\n\n// Inject our special stylesheet from dcp-client only if we're on the portal webpage.\nif (typeof window !== 'undefined' && typeof document !== 'undefined' && dcpConfig.portal.location.hostname === window.location.hostname) {\n // <link rel='stylesheet' href='/css/dashboard.css'>\n const stylesheet = document.createElement('link')\n stylesheet.rel = 'stylesheet'\n // Needed for the duplicate check done later.\n stylesheet.id = 'dcp-modal-styles'\n\n const dcpClientBundle = document.getElementById('_dcp_client_bundle')\n let src\n if (dcpClientBundle) {\n src = dcpClientBundle.src.replace('dcp-client-bundle.js', 'dcp-modal-style.css')\n } else {\n src = dcpConfig.portal.location.href + 'dcp-client/dist/dcp-modal-style.css'\n }\n\n stylesheet.href = src\n // If the style was injected before, don't inject it again.\n // Could occur when loading a file that imports Modal.js and loading\n // comput.min.js in the same HTML file.\n if (document.getElementById(stylesheet.id) === null) {\n document.getElementsByTagName('head')[0].appendChild(stylesheet)\n }\n\n if (typeof {\"version\":\"0bf54dbe88ee11e84b28e62b73cbd154d06967ea\",\"branch\":\"release\",\"dcpClient\":{\"version\":\"4.2.22\",\"from\":\"git+ssh://git@gitlab.com/Distributed-Compute-Protocol/dcp-client.git#prod-20221115\",\"resolved\":\"git+ssh://git@gitlab.com/Distributed-Compute-Protocol/dcp-client.git#295f18d25636449034433ddd3789070a5406c98e\"},\"built\":\"Mon Nov 21 2022 11:41:21 GMT-0500 (Eastern Standard Time)\",\"config\":{\"generated\":\"Mon 21 Nov 2022 11:41:19 AM EST by erose on lorge\",\"build\":\"debug\"},\"webpack\":\"5.74.0\",\"node\":\"v14.21.1\"} !== 'undefined' && typeof window.Modal === 'undefined') {\n window.Modal = Modal\n }\n}\n\n\n//# sourceURL=webpack://dcp/./portal/www/js/modal.js?");
|
|
3798
3871
|
|
|
3799
3872
|
/***/ }),
|
|
3800
3873
|
|
|
@@ -3959,7 +4032,7 @@ eval("/* provided dependency */ var process = __webpack_require__(/*! ./node_mod
|
|
|
3959
4032
|
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
|
3960
4033
|
|
|
3961
4034
|
"use strict";
|
|
3962
|
-
eval("/**\n * @file dcp-url.js Module for working with URLs; primarily intended for\n * use in daemon configuration etc. Works in Node 10 and\n * current browsers. Probably works older Node, too.\n *\n * @group core-api\n * @module dcp-url\n * @author Wes Garland, wes@kingsds.network\n * @date May 2019\n */\n\n\n\nconst { assertIsA } = __webpack_require__(/*! ./dcp-assert */ \"./src/common/dcp-assert.js\");\nconst inspect = Symbol.for('nodejs.util.inspect.custom');\n\n/** Class for working with URLs; primarily intended for use in creating and communicating with\n * DCP daemons; used by the configuration library to implement the url() function.\n *\n * @constructor\n * @param url A string describing a full URL, e.g. \"http://localhost/\". Will be\n * parsed by Node's url.parse().\n * This object can be passed as an options argument to a NodeJS server\n * constructor, or used like 'window.location'.\n *\n * @returns {object} An instance of exports.DcpURL with at least the following properties:\n * <ul>\n * <li> hostname\n * <li> port\n * <li> protocol\n * <li> pathname\n * <li> href\n * <li> origin\n * </ul>\n *//** Class for working with URLs; primarily intended for use in creating and communicating with\n * DCP daemons; used by the configuration library to implement the url() function.\n *\n * @constructor\n * @param {object} An instance of URL, or the return value of Node's url.parse\n * @returns {object} An instance of exports.URL with at least the following properties:\n * <ul>\n * <li> hostname\n * <li> port\n * <li> protocol\n * <li> pathname\n * <li> href\n * <li> origin\n * </ul>\n */\nexports.DcpURL = function dcpUrl$$DcpURL(url, __steal) {\n var backingStore = {};\n var w3cUrl;\n\n if (typeof url === \"object\") {\n w3cUrl = __steal ? url : new URL(url.href);\n } else {\n assertIsA(url, 'string');\n w3cUrl = new URL(url);\n }\n\n function copy(target, source) {\n for (let prop in source) {\n if (typeof source[prop] !== 'undefined' && typeof source[prop] !== 'function')\n target[prop] = source[prop];\n }\n }\n copy(backingStore, w3cUrl);\n\n function munge(recalcProp) {\n /* make use of W3C URL magic getters/setters */\n if (recalcProp) {\n w3cUrl[recalcProp] = backingStore[recalcProp];\n copy(backingStore, w3cUrl);\n }\n\n if (!backingStore.protocol || (!backingStore.hostname && backingStore.protocol !== 'file:' && backingStore.protocol !== 'data:'))\n throw new Error(`Invalid URL: '${w3cUrl}`);\n\n if (backingStore.protocol === 'data:')\n backingStore.origin = null;\n if (backingStore.port)\n backingStore.port = +backingStore.port;\n \n if (!backingStore.port) {\n switch(backingStore.protocol) {\n case 'http:':\n backingStore.port = 80;\n break;\n case 'https:':\n backingStore.port = 443;\n break;\n case 'ftp:':\n backingStore.port = 21;\n break;\n case 'ftps:':\n backingStore.port = 990;\n break;\n }\n } \n\n switch (backingStore.hostname.toLowerCase())\n {\n case '::':\n case 'inaddr_any':\n case 'any/0':\n case 'any':\n backingStore.hostname = 'inaddr_any';\n break;\n } \n }\n munge();\n\n /* @todo - this would be better off solved by a Proxy of URL,\n * but there are enough hairy bits throughout the system\n * that such a refactor is risky without significant\n * testing. In particular, we need to be careful about\n * instanceof DcpURL vs URL, dcpConfig patchups, and\n * maintaining .resolve, data:, and numeric ports. /wg Feb 2021\n */\n for (let p of Object.getOwnPropertyNames(backingStore)) {\n Object.defineProperty(this, p, {\n enumerable: true,\n configurable: true,\n get: () => backingStore[p],\n set: (value) => {\n backingStore[p] = value;\n munge(p);\n }\n });\n }\n\n /* Definte searchParams in terms of search in a way that is iterable and mutable */\n delete this.searchParams;\n this.searchParams = decodeSearchParams(this.search);\n\n /* Define a replacement search method which accounts for muteable searchParams */\n delete this.search;\n Object.defineProperty(this, 'search', {\n configurable: false,\n enumerable: true,\n get: () => {\n let entries = Object.entries(this.searchParams);\n\n if (entries.length === 0)\n return '';\n\n return '?' + entries.map((kvp) => `${encodeURIComponent(kvp[0])}=${encodeURIComponent(kvp[1])}`).join('&');\n },\n set: (value) => {\n this.searchParams = decodeSearchParams(value);\n },\n });\n\n /* Define a replacement search method which accounts for muteable searchParams */\n delete this.href;\n Object.defineProperty(this, 'href', {\n configurable: false,\n enumerable: true,\n get: () => {\n let origin = this.protocol === 'file:' ? 'file://' : this.origin;\n return origin + this.pathname + this.search + this.hash;\n }\n });\n}\n\nexports.DcpURL.prototype[inspect] = function dcpURL$$URL$inspect() {\n return '[object ' + this.constructor.name + ' ' + this.toString() + ']';\n}\n\n/** \n * @returns the full text version of URL \n */\nexports.DcpURL.prototype.toString = function dcpURL$$URL$toString() {\n return this.href\n}\n\n/** \n * @returns the full text version of URL \n */\nexports.DcpURL.prototype.valueOf = function dcpURL$$URL$valueOf() {\n return this.href\n}\n\n/** Form 1: Compare two URLs to see if they match origins per the Same-Origin Policy.\n * @see https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy\n *\n * @param {string} otherURL The URL whose origin we are comparing to this one.\n * It will be parsed using the same rules as strings\n * passed into the exports.URL() constructor.\n *//** Form 2: Compare two URLs to see if they match origins per the Same-Origin Policy.\n * @see https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy\n *\n * @param {object} otherURL The URL whose origin we are comparing to this one.\n * Can be an instance of exports.URL() or a similar\n * object, such as the result of Node url.parse(),\n * browser's window.location, etc.\n */\nexports.DcpURL.prototype.sameOrigin = function dcpURL$$URL$sameOrigin(otherURL) {\n if (typeof otherURL === \"string\")\n otherURL = new (exports.DcpURL)(otherURL)\n\n if (this.protocol === 'file:')\n return false;\n \n return this.protocol === otherURL.protocol && this.hostname === otherURL.hostname && this.port === otherURL.port\n}\n \n/** Resolves a target URL relative to the URL described by this instance in a manner similar to that\n * of a Web browser resolving an anchor tag HREF.\n * \n * @param {string} path The HREF URL being resolved\n * @returns {string} The new URL string\n */\nexports.DcpURL.prototype.resolve = function dcpURL$$URL$resolve(path) {\n return (new URL(path, this.href)).href;\n}\n\n/** Resolves a target URL relative to the URL described by this instance in a manner similar to that\n * of a Web browser resolving an anchor tag HREF.\n * \n * @param {string} path The HREF URL being resolved\n * @returns {string} The new URL as an instance of DcpURL\n */\nexports.DcpURL.prototype.resolveUrl = function dcpURL$$URL$resolveUrl(path) {\n return new exports.DcpURL(new URL(path, this.href), true /* __steal */);\n}\n\nexports.DcpURL.prototype.clone = function dcpURL$$URL$clone(url) {\n return new this.constructor(this.href);\n}\n \n/** Patch an object graph, transforming instances of URL into instances of exports.URL.\n graphs are supported; depth limited to stack size. Edges are composed of\n * own properties.\n * \n * @param obj {object} The object graph to patch\n * @param seen {Array} An array of objects which should not be traversed \n */\nexports.patchup = function dcpURL$URL$patchObject(obj, seen) {\n if (!seen)\n seen = []\n seen.push(obj)\n\n Object.keys(obj).forEach((p) => {\n if ((typeof obj[p] !== 'object') || (obj[p] === null) || seen.indexOf(obj[p]) !== -1)\n return\n \n /**\n * Checking the constructor's name in case the object is an instance of a\n * DcpURL created in a different context, preventing potential `instanceof`\n * checks in the future from failing.\n */\n if (obj[p] instanceof URL || obj[p].constructor.name === 'dcpUrl$$URL')\n obj[p] = new (exports.DcpURL)(obj[p])\n else\n exports.patchup(obj[p], seen)\n })\n}\n\n/** Tests if an object is either a URL object or a DcpURL. \n * \n * @param {object} obj The object to test\n * @returns {boolean} True if the object is a URL or DcpURl, otherwise False\n */\nexports.DcpURL.isURL = function dcpURL$URL$isaURL(obj) {\n if (obj === undefined || obj === null) {\n return false;\n }\n\n return (\n obj instanceof URL ||\n obj instanceof exports.DcpURL\n )\n}\n\n/**\n * Take a url search string and return a searchParams object\n */\nfunction decodeSearchParams(search)\n{\n var searchParams = {};\n \n if (search && search.length)\n {\n for (let kvp of search.slice(1).split('&'))\n {\n let [ key, value ] = kvp.split('=');\n searchParams[key] = value && decodeURIComponent(value.replace(/\\+/g, ' '));\n }\n }\n\n return searchParams;\n}\n\n\n//# sourceURL=webpack://dcp/./src/common/dcp-url.js?");
|
|
4035
|
+
eval("/**\n * @file dcp-url.js Module for working with URLs; primarily intended for\n * use in daemon configuration etc. Works in Node 10 and\n * current browsers. Probably works older Node, too.\n *\n * @group core-api\n * @module dcp-url\n * @author Wes Garland, wes@kingsds.network\n * @date May 2019\n */\n\n\n\nconst { assertIsA } = __webpack_require__(/*! ./dcp-assert */ \"./src/common/dcp-assert.js\");\nconst inspect = Symbol.for('nodejs.util.inspect.custom');\n\n/** Class for working with URLs; primarily intended for use in creating and communicating with\n * DCP daemons; used by the configuration library to implement the url() function.\n *\n * @constructor\n * @param url A string describing a full URL, e.g. \"http://localhost/\". Will be\n * parsed by Node's url.parse().\n * This object can be passed as an options argument to a NodeJS server\n * constructor, or used like 'window.location'.\n *\n * @returns {object} An instance of exports.DcpURL with at least the following properties:\n * <ul>\n * <li> hostname\n * <li> port\n * <li> protocol\n * <li> pathname\n * <li> href\n * <li> origin\n * </ul>\n *//** Class for working with URLs; primarily intended for use in creating and communicating with\n * DCP daemons; used by the configuration library to implement the url() function.\n *\n * @constructor\n * @param {object} An instance of URL, or the return value of Node's url.parse\n * @returns {object} An instance of exports.URL with at least the following properties:\n * <ul>\n * <li> hostname\n * <li> port\n * <li> protocol\n * <li> pathname\n * <li> href\n * <li> origin\n * </ul>\n */\nexports.DcpURL = function dcpUrl$$DcpURL(url, __steal) {\n var backingStore = {};\n var w3cUrl;\n\n if (typeof url === \"object\") {\n w3cUrl = __steal ? url : new URL(url.href);\n } else {\n assertIsA(url, 'string');\n w3cUrl = new URL(url);\n }\n\n function copy(target, source) {\n for (let prop in source) {\n if (typeof source[prop] !== 'undefined' && typeof source[prop] !== 'function')\n target[prop] = source[prop];\n }\n }\n copy(backingStore, w3cUrl);\n\n function munge(recalcProp) {\n /* make use of W3C URL magic getters/setters */\n if (recalcProp) {\n w3cUrl[recalcProp] = backingStore[recalcProp];\n copy(backingStore, w3cUrl);\n }\n\n if (!backingStore.protocol || (!backingStore.hostname && backingStore.protocol !== 'file:' && backingStore.protocol !== 'data:'))\n throw new Error(`Invalid URL: '${w3cUrl}`);\n\n if (backingStore.protocol === 'data:')\n backingStore.origin = null;\n if (backingStore.port)\n backingStore.port = +backingStore.port;\n \n if (!backingStore.port) {\n switch(backingStore.protocol) {\n case 'http:':\n backingStore.port = 80;\n break;\n case 'https:':\n backingStore.port = 443;\n break;\n case 'ftp:':\n backingStore.port = 21;\n break;\n case 'ftps:':\n backingStore.port = 990;\n break;\n }\n } \n\n switch (backingStore.hostname.toLowerCase())\n {\n case '::':\n case 'inaddr_any':\n case 'any/0':\n case 'any':\n backingStore.hostname = 'inaddr_any';\n break;\n } \n }\n munge();\n\n /* @todo - this would be better off solved by a Proxy of URL,\n * but there are enough hairy bits throughout the system\n * that such a refactor is risky without significant\n * testing. In particular, we need to be careful about\n * instanceof DcpURL vs URL, dcpConfig patchups, and\n * maintaining .resolve, data:, and numeric ports. /wg Feb 2021\n */\n for (let p of Object.getOwnPropertyNames(backingStore)) {\n Object.defineProperty(this, p, {\n enumerable: true,\n configurable: true,\n get: () => backingStore[p],\n set: (value) => {\n backingStore[p] = value;\n munge(p);\n }\n });\n }\n\n /* Definte searchParams in terms of search in a way that is iterable and mutable */\n delete this.searchParams;\n this.searchParams = decodeSearchParams(this.search);\n\n /* Define a replacement search method which accounts for muteable searchParams */\n delete this.search;\n Object.defineProperty(this, 'search', {\n configurable: false,\n enumerable: true,\n get: () => {\n let entries = Object.entries(this.searchParams);\n\n if (entries.length === 0)\n return '';\n\n return '?' + entries.map((kvp) => `${encodeURIComponent(kvp[0])}=${encodeURIComponent(kvp[1])}`).join('&');\n },\n set: (value) => {\n this.searchParams = decodeSearchParams(value);\n },\n });\n\n /* Define a replacement search method which accounts for muteable searchParams */\n delete this.href;\n Object.defineProperty(this, 'href', {\n configurable: false,\n enumerable: true,\n get: () => {\n let origin = this.protocol === 'file:' ? 'file://' : this.origin;\n return origin + this.pathname + this.search + this.hash;\n }\n });\n}\n\nexports.DcpURL.prototype[inspect] = function dcpURL$$URL$inspect() {\n return '[object ' + this.constructor.name + ' ' + this.toString() + ']';\n}\n\n/** \n * @returns the full text version of URL \n */\nexports.DcpURL.prototype.toString = function dcpURL$$URL$toString() {\n return this.href\n}\n\n/** \n * @returns the full text version of URL \n */\nexports.DcpURL.prototype.valueOf = function dcpURL$$URL$valueOf() {\n return this.href\n}\n\n/**\n * @returns 'prepared' version of URL for kvin use\n */\nexports.DcpURL.prototype.toKVIN = function dcpURL$$URL$toKVIN(_, kvin) {\n kvin.userCtors[exports.DcpURL.name] = exports.DcpURL;\n return { ctr: exports.DcpURL.name, arg: this.href };\n}\n\n/** Form 1: Compare two URLs to see if they match origins per the Same-Origin Policy.\n * @see https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy\n *\n * @param {string} otherURL The URL whose origin we are comparing to this one.\n * It will be parsed using the same rules as strings\n * passed into the exports.URL() constructor.\n *//** Form 2: Compare two URLs to see if they match origins per the Same-Origin Policy.\n * @see https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy\n *\n * @param {object} otherURL The URL whose origin we are comparing to this one.\n * Can be an instance of exports.URL() or a similar\n * object, such as the result of Node url.parse(),\n * browser's window.location, etc.\n */\nexports.DcpURL.prototype.sameOrigin = function dcpURL$$URL$sameOrigin(otherURL) {\n if (typeof otherURL === \"string\")\n otherURL = new (exports.DcpURL)(otherURL)\n\n if (this.protocol === 'file:')\n return false;\n \n return this.protocol === otherURL.protocol && this.hostname === otherURL.hostname && this.port === otherURL.port\n}\n \n/** Resolves a target URL relative to the URL described by this instance in a manner similar to that\n * of a Web browser resolving an anchor tag HREF.\n * \n * @param {string} path The HREF URL being resolved\n * @returns {string} The new URL string\n */\nexports.DcpURL.prototype.resolve = function dcpURL$$URL$resolve(path) {\n return (new URL(path, this.href)).href;\n}\n\n/** Resolves a target URL relative to the URL described by this instance in a manner similar to that\n * of a Web browser resolving an anchor tag HREF.\n * \n * @param {string} path The HREF URL being resolved\n * @returns {string} The new URL as an instance of DcpURL\n */\nexports.DcpURL.prototype.resolveUrl = function dcpURL$$URL$resolveUrl(path) {\n return new exports.DcpURL(new URL(path, this.href), true /* __steal */);\n}\n\nexports.DcpURL.prototype.clone = function dcpURL$$URL$clone(url) {\n return new this.constructor(this.href);\n}\n \n/** Patch an object graph, transforming instances of URL into instances of exports.URL.\n graphs are supported; depth limited to stack size. Edges are composed of\n * own properties.\n * \n * @param obj {object} The object graph to patch\n * @param seen {Array} An array of objects which should not be traversed \n */\nexports.patchup = function dcpURL$URL$patchObject(obj, seen) {\n if (!seen)\n seen = []\n seen.push(obj)\n\n Object.keys(obj).forEach((p) => {\n if ((typeof obj[p] !== 'object') || (obj[p] === null) || seen.indexOf(obj[p]) !== -1)\n return\n \n /**\n * Checking the constructor's name in case the object is an instance of a\n * DcpURL created in a different context, preventing potential `instanceof`\n * checks in the future from failing.\n */\n if (obj[p] instanceof URL || obj[p].constructor.name === 'dcpUrl$$URL')\n obj[p] = new (exports.DcpURL)(obj[p])\n else\n exports.patchup(obj[p], seen)\n })\n}\n\n/** Tests if an object is either a URL object or a DcpURL. \n * \n * @param {object} obj The object to test\n * @returns {boolean} True if the object is a URL or DcpURl, otherwise False\n */\nexports.DcpURL.isURL = function dcpURL$URL$isaURL(obj) {\n if (obj === undefined || obj === null) {\n return false;\n }\n\n return (\n obj instanceof URL ||\n obj instanceof exports.DcpURL\n )\n}\n\n/**\n * Take a url search string and return a searchParams object\n */\nfunction decodeSearchParams(search)\n{\n var searchParams = {};\n \n if (search && search.length)\n {\n for (let kvp of search.slice(1).split('&'))\n {\n let [ key, value ] = kvp.split('=');\n searchParams[key] = value && decodeURIComponent(value.replace(/\\+/g, ' '));\n }\n }\n\n return searchParams;\n}\n\n\n//# sourceURL=webpack://dcp/./src/common/dcp-url.js?");
|
|
3963
4036
|
|
|
3964
4037
|
/***/ }),
|
|
3965
4038
|
|
|
@@ -4100,7 +4173,7 @@ eval("/**\n * @file password.js\n * Modal providing a way to
|
|
|
4100
4173
|
\**********************************************/
|
|
4101
4174
|
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
|
4102
4175
|
|
|
4103
|
-
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=
|
|
4176
|
+
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=0bf54dbe88ee11e84b28e62b73cbd154d06967ea'; /* 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?");
|
|
4104
4177
|
|
|
4105
4178
|
/***/ }),
|
|
4106
4179
|
|
|
@@ -4111,7 +4184,7 @@ eval("/**\n * @file client-modal/utils.js\n * @author KC Erb\n * @date Mar 2020\
|
|
|
4111
4184
|
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
|
4112
4185
|
|
|
4113
4186
|
"use strict";
|
|
4114
|
-
eval("/**\n * @file Client facing module that implements Compute Groups API\n * @module dcp/compute-groups\n * @access public\n * @author Kayra E-A <kayra@kingsds.network>\n * Wes Garland <wes@kingsds.network>\n * Paul <paul@kingsds.network>\n * @date Sept 2020\n * February 2022\n * May 2022\n */\n\n\nconst protocolV4 = __webpack_require__(/*! dcp/protocol-v4 */ \"./src/protocol-v4/index.js\");\nconst wallet = __webpack_require__(/*! dcp/dcp-client/wallet */ \"./src/dcp-client/wallet/index.js\");\nconst hash = __webpack_require__(/*! ../../common/hash */ \"./src/common/hash.js\");\nconst { DCPError } = __webpack_require__(/*! ../../common/dcp-error */ \"./src/common/dcp-error.js\");\nconst debugging = (__webpack_require__(/*! dcp/debugging */ \"./src/debugging.js\").scope)('scheduler');\nconst { Address } = __webpack_require__(/*! dcp/dcp-client/wallet */ \"./src/dcp-client/wallet/index.js\");\nconst constants = __webpack_require__(/*! dcp/common/scheduler-constants */ \"./src/common/scheduler-constants.js\");\nconst { clientError, reconstructServiceError } = __webpack_require__(/*! dcp/utils */ \"./src/utils/index.js\");\n\n/** @typedef {import('dcp/utils').apiServiceType} apiServiceType */\n/** @typedef {import('dcp/utils').apiClientType} apiClientType */\n/** @typedef {string} opaqueId */\n\n/**\n * @typedef {object} cgAccessType\n * @property {opaqueId} [id]\n * @property {string} [joinKey]\n */\n\n/**\n * @typedef {object} cgClientJoinType\n * @property {opaqueId} [id]\n * @property {Address} [joinAddress]\n * @property {string} [joinKey]\n * @property {string} [joinSecret]\n * @property {string} [joinHash]\n */\n\n/**\n * @typedef {object} cgServiceJoinType\n * @property {opaqueId} [id]\n * @property {Address} [joinAddress]\n * @property {string} [joinKey]\n * @property {string} [joinHashHash]\n */\n\n/**\n * Establishes the client connection to the computeGroups microservice if it does not exist already from the default config.\n * \n * @returns {protocolV4.Connection}\n * @access public\n * @example\n * const result = await exports.serviceConnection.send('createGroup', {\n name: name,\n description: description,\n });\n */\n\nexports.serviceConnection = null;\n\n//\n// Reference counting pattern:\n// For every time addRef is called,\n// closeServiceConnection must eventually be called.\n// Reference counting allows multiple execs in a Promise.all .\n//\nvar refCount = 0;\nexports.addRef = function addRef() {\n refCount++;\n}\n\nconst openAndConnectServiceConn = async function openAndConnectServiceConn()\n{\n exports.serviceConnection = new protocolV4.Connection(dcpConfig.scheduler.services.computeGroups);\n exports.serviceConnection.on('close', openAndConnectServiceConn);\n await exports.serviceConnection.connect();\n refCount = 0; // Help with sanity.\n}\n\n/**\n * Resets the client connection to the computeGroups microservice.\n */\nexports.closeServiceConnection = async function closeServiceConnection() {\n if (refCount > 0) refCount--;\n if (exports.serviceConnection && refCount < 1)\n {\n exports.serviceConnection.off('close', openAndConnectServiceConn);\n exports.serviceConnection.close(null, true);\n refCount = 0; // Help with sanity.\n exports.serviceConnection = null;\n }\n};\n\n/**\n * (Used in jobs/index.js)\n * KeepAlive for the service connection to compute groups.\n */\nexports.keepAlive = async function keepAlive() {\n if (!exports.serviceConnection)\n await openAndConnectServiceConn();\n\n exports.serviceConnection.keepalive().catch(err => console.error('Warning: keepalive failed for compute groups service', err));\n}\n\n/**\n * Checks whether descriptor corresponds to the public compute group from the scheduler constants.\n */\nexports.isPublicComputeGroup = function isPublicComputeGroup(descriptor) {\n return descriptor.id === constants.computeGroups.public.id\n && descriptor.opaqueId === constants.computeGroups.public.opaqueId;\n};\n\n/**\n * Returns a compute group identification snippet for diagnostic messages,\n * @param {object} descriptor - Must have one of the properties joinKey, id (id:=opaqueId). Specifically\n * descriptor = { joinKey: 'dcpDemo' } or descriptor = { id: 'bYcYGQ3NOpFnP4FKs6IBQd' },\n * where the corresponding row in table computeGroups have attributes\n * joinKey:='dcpDemo' or opaqueId:='bYcYGQ3NOpFnP4FKs6IBQd' .\n * @returns {string}\n */\nfunction cgId(descriptor) {\n return (descriptor.joinKey) ? `joinKey ${descriptor.joinKey}` : `id ${descriptor.id}`;\n}\n\n/**\n * Verify sufficient information in descriptor to access a compute group.\n * Emit diagnostics about unnecessary information.\n * @param {cgAccessType} descriptor \n * @param {string} methodName \n */\nfunction validateCGDescriptor(descriptor, methodName) {\n for (const prop in descriptor) {\n if ([ 'id', 'joinKey' ].includes(prop)) continue;\n if ([ 'joinAddress', 'joinHash', 'joinSecret' ].includes(prop))\n console.warn(`It is not necessary to specify '${prop}' in the descriptor ${JSON.stringify(descriptor)} when calling ${methodName}`);\n else\n console.error(`Do not specify '${prop}' in the descriptor ${JSON.stringify(descriptor)} when calling ${methodName}`);\n }\n}\n\n/**\n * Verify sufficient information in descriptor to authorize a compute group.\n * Emit diagnostics about unnecessary information.\n * @param {cgClientJoinType} joinDescriptor \n * @param {string} methodName \n */\nfunction validateCGJoinDescriptor(joinDescriptor, methodName) {\n for (const prop in joinDescriptor) {\n if ([ 'id', 'joinKey', 'joinSecret', 'joinHash', 'joinAddress' ].includes(prop)) continue;\n console.error(`Do not specify '${prop}' in the descriptor ${JSON.stringify(joinDescriptor)} when calling ${methodName}`);\n }\n}\n\n/**\n * Build message to go across the wire.\n * Verify sufficient information in descriptor to access a compute group.\n * Emit diagnostics about unnecessary information.\n * @param {cgAccessType} descriptor\n * @param {string} methodName\n * @returns {cgAccessType}\n */\nfunction buildCGMessage(descriptor, methodName)\n{\n if (exports.isPublicComputeGroup(descriptor)) return descriptor;\n\n const message = {};\n // Construct message.joinKey xor message.id .\n if (descriptor.joinKey) message.joinKey = descriptor.joinKey;\n else if (descriptor.id) message.id = descriptor.id; // id:=opaqueId\n\n debugging('computeGroups') && console.debug(`${methodName}:buildCGMessage: descriptor`, descriptor, 'message', message);\n\n validateCGDescriptor(descriptor, methodName);\n\n return message;\n}\n\n/**\n * Build message so that joinHash, joinSecret, opaqueId do not go across the wire.\n * Verify sufficient information in descriptor to authorize a compute group.\n * Emit diagnostics about unnecessary information.\n * @param {cgClientJoinType} descriptor\n * @param {string} methodName\n * @returns {cgServiceJoinType}\n */\nfunction buildCGJoinMessage(descriptor, methodName)\n{\n if (exports.isPublicComputeGroup(descriptor)) return descriptor;\n\n const message = {};\n // Construct message.joinKey xor message.id .\n if (descriptor.joinKey) message.joinKey = descriptor.joinKey;\n else if (descriptor.id) message.id = descriptor.id; // id:=opaqueId\n // Construct message.joinAddress .\n if (descriptor.joinAddress) message.joinAddress = descriptor.joinAddress;\n\n debugging('computeGroups') && console.debug(`${methodName}:buildCGJoinMessage: descriptor`, descriptor, 'message', message);\n\n validateCGJoinDescriptor(descriptor, methodName);\n\n // Construct message.joinHashHash .\n if (descriptor.joinSecret) message.joinHashHash = hash.calculate(hash.eh1, exports.calculateJoinHash(descriptor), exports.serviceConnection.dcpsid);\n if (descriptor.joinHash) message.joinHashHash = hash.calculate(hash.eh1, descriptor.joinHash, exports.serviceConnection.dcpsid);\n\n return message;\n}\n\nfunction hasSufficientJoinInfo(joinDescriptor) {\n // Verify joinDescriptor has sufficient information to authorize a compute group (not guarenteed).\n return (joinDescriptor.joinKey && (joinDescriptor.joinSecret || joinDescriptor.joinHash))\n || (joinDescriptor.id && joinDescriptor.joinAddress)\n || exports.isPublicComputeGroup(joinDescriptor);\n}\n\nconst newCGPrototype = { type: 'object',\n parameters: {\n // name: { type: 'string', default: undefined }, /* name of group (length <= 255) */\n // description: { type: 'string', default: undefined }, /* description of group (length <= 255) */\n // id: { type: 'string', default: undefined }, /* opaqueId, the unique identifier of the compute group; nanoid (length === 22) */\n // joinKey: { type: 'string', default: undefined }, /* basically the login (length <= 255) */\n // joinSecret: { type: 'string', default: undefined }, /* basically the password (length <= 255) */\n // joinHash: { type: 'string', default: undefined }, /* basically the password, the joinSecret seeded & hashed */\n // joinAddress: { type: Address, default: undefined }, /* signature gives alternative to login/password */\n\n commissionRate: { type: 'BigNumber', default: undefined }, /* commission, see DCP-1889 */\n deployFee: { type: 'BigNumber', default: undefined }, /* number of DCC to take for every deployment */\n deployAccess: { type: 'string', default: undefined }, /* can be \"owner\"|\"join\" (dcp-1910) */\n addJobFee: { type: 'BigNumber', default: undefined }, /* fee required each time a job joins a compute group */\n maxTotalPayment: { type: 'BigNumber', default: undefined }, /* limit on maximum job payment, NULL => Infinity */\n\n /* Administrative limits on group. NULL => Infinity: Should all be integers or undefined. */\n maxConcurrentJobs: { type: 'number', default: undefined },\n maxConcurrentWorkers: { type: 'number', default: undefined },\n maxConcurrentSandboxes: { type: 'number', default: undefined },\n maxConcurrentCPUs: { type: 'number', default: undefined },\n maxConcurrentGPUs: { type: 'number', default: undefined },\n maxConcurrentEscrow: { type: 'BigNumber', default: undefined },\n },\n};\n\n/**\n * Async function that creates a new Compute Group.\n *\n * The joinDescriptor is of the form { joinKey, joinSecret }, { joinKey, joinHash } or { id, joinAddress }.\n * where id will correspond to the attribute opaqueId in the new row in the computeGroups table.\n *\n * This function can only be called with ADMIN permission.\n * Properties not appearing in newCGPrototype.parameters are not allowed in otherProperties.\n *\n * @param {cgClientJoinType} joinDescriptor - Must have properly defined { joinKey, joinSecret }, { joinKey, joinHash }\n * or { id, joinAddress }, where id will correspond to the attribute opaqueId\n * in the new row in the computeGroups table.\n * @param {string} [name] - The name of the compute group.\n * @param {string} [description] - The description of the compute group.\n * @param {object} [otherProperties] - The 5 attributes of table computeGroup related to commissions and fees.\n * commissionRate: notNull(zFinNum),// commission, see DCP-1889\n * deployFee: notNull(zFinNum),// number of DCC to take for every deployment\n * deployAccess: string, // can be \"owner\"|\"join\" (dcp-1910)\n * addJobFee: notNull(zFinNum),// fee required each time a job joins a compute group\n * maxTotalPayment: finNum, // limit on maximum job payment, NULL => Infinity\n * And the 6 attributes of table computeGroup related to limits.\n * maxConcurrentJobs: integer,\n * maxConcurrentWorkers: integer,\n * maxConcurrentSandboxes: integer,\n * maxConcurrentCPUs: integer,\n * maxConcurrentGPUs: integer,\n * maxConcurrentEscrow: finNum,\n * @returns {Promise<apiClientType>} - { success, payload: computeGroup.id }\n * @access public\n * @example\n * await computeGroup.createGroup({ joinKey: 'dcpDemo', joinSecret: 'theSecret' }, 'myCGName', 'myCGDescription', { deployFee: 0.00015 });\n * await computeGroup.createGroup({ joinKey: 'dcpDemo2', joinHash: 'eh1-e063976b20a92da97a27b9873465c6f2c9d6e4370befa86c8c1dd312c78befc2' });\n * await computeGroup.createGroup({ id: 'bYcYGQ3NOpFnP4FKs6IBQd', joinAddress: 'c15053fc30d4bdf91e2e0bba79578f8b649e55ea' });\n * where the corresponding rows in table computeGroups have attributes\n * row1: joinKey:='dcpDemo',\n * row2: joinKey:='dcpDemo2', joinHash:='eh1-e063976b20a92da97a27b9873465c6f2c9d6e4370befa86c8c1dd312c78befc2'\n * row3: opaqueId:='bYcYGQ3NOpFnP4FKs6IBQd', joinAddress:='c15053fc30d4bdf91e2e0bba79578f8b649e55ea' .\n */\nexports.createGroup = async function createGroup(joinDescriptor, name, description, otherProperties)\n{\n if (!exports.serviceConnection)\n await openAndConnectServiceConn();\n\n validateCGJoinDescriptor(joinDescriptor, 'createGroup');\n\n // Verify joinDescriptor has sufficient information to authorize a compute group (not guarenteed).\n if (!hasSufficientJoinInfo(joinDescriptor))\n return clientError(`createGroup: Insufficient information to authorize compute group: ${JSON.stringify(joinDescriptor)}.`);\n\n // Validate the properties in otherProperties.\n for (const methodName in otherProperties) {\n if (!Object.keys(newCGPrototype.parameters).includes(methodName))\n return clientError(`createGroup: Property ${methodName} cannot be speicfied in otherProperties. Can only specify ${JSON.stringify(Object.keys(newCGPrototype.parameters))}`);\n }\n\n // Translate joinSecret to joinHash.\n if (joinDescriptor.joinSecret) {\n joinDescriptor.joinHash = exports.calculateJoinHash(joinDescriptor);\n delete joinDescriptor.joinSecret;\n }\n\n if (otherProperties && (otherProperties.commissionRate < 0 || otherProperties.commissionRate >= 1))\n return clientError(`client-createGroup: commissionRate ${otherProperties.commissionRate} must be between 0 and 1 (0 <= commissionRate < 1).`);\n\n debugging('computeGroups') && console.debug('client-createGroup: input:', joinDescriptor, name, description, otherProperties);\n\n const { success, payload } = await exports.serviceConnection.send('createGroup', { joinDescriptor, name, description, otherProperties });\n\n if (!success) return clientError(`Cannot create new compute group, with ${cgId(joinDescriptor)}.`);\n if (payload && !payload.success) return reconstructServiceError(payload);\n\n debugging('computeGroups') && console.debug('client-createGroup: payload', payload);\n\n return payload;\n};\n\nconst changeCGPrototype = { type: 'object',\n parameters: {\n name: { type: 'string', default: undefined }, /* name of group (length <= 255) */\n description: { type: 'string', default: undefined }, /* description of group (length <= 255) */\n joinHash: { type: 'string', default: undefined }, /* basically the password, seeded & hashed (length <= 255) */\n joinAddress: { type: Address, default: undefined }, /* signature gives alternative to login/password */\n\n commissionRate: { type: 'BigNumber', default: undefined }, /* commission, see DCP-1889 */\n deployFee: { type: 'BigNumber', default: undefined }, /* number of DCC to take for every deployment */\n deployAccess: { type: 'string', default: undefined }, /* can be \"owner\"|\"join\" (dcp-1910) */\n addJobFee: { type: 'BigNumber', default: undefined }, /* fee required each time a job joins a compute group */\n maxTotalPayment: { type: 'BigNumber', default: undefined }, /* limit on maximum job payment, NULL => Infinity */\n\n /* Administrative limits on group. NULL => Infinity: Should all be integers or undefined. */\n maxConcurrentJobs: { type: 'number', default: undefined },\n maxConcurrentWorkers: { type: 'number', default: undefined },\n maxConcurrentSandboxes: { type: 'number', default: undefined },\n maxConcurrentCPUs: { type: 'number', default: undefined },\n maxConcurrentGPUs: { type: 'number', default: undefined },\n maxConcurrentEscrow: { type: 'BigNumber', default: undefined },\n },\n};\n\n/**\n * Async function that changes a new Compute Group.\n * \n * The parameter newDescriptor contains the new property values,\n * and the properties that are allowed to be changed appear in changeCGPrototype.parameters.\n * \n * The descriptor must have joinKey or id, where id:=opaqueId.\n * Must own the compute group or be ADMIN to use changeGroup.\n * \n * @param {cgAccessType} descriptor - Must have joinkey or id, where id:=opaqueId.\n * @param {object} newDescriptor - Properties not appearing in changeCGPrototype.parameters are not allowed.\n * @returns {Promise<apiClientType>}\n * await computeGroup.changeGroup({ joinKey: 'dcpDemo' }, { joinSecret: 'myNewPasswrd' });\n * await computeGroup.changeGroup({ id: 'bYcYGQ3NOpFnP4FKs6IBQd' }, { name: 'myNewName', deployFee: 0.0001 });\n * where the corresponding rows in table computeGroups have attributes\n * row1: joinKey:='dcpDemo',\n * row2: opaqueId:='bYcYGQ3NOpFnP4FKs6IBQd'\n */\nexports.changeGroup = async function changeGroup(descriptor, newDescriptor)\n{\n if (!exports.serviceConnection)\n await openAndConnectServiceConn();\n\n // Verify descriptor has sufficient information to access a compute group (not guarenteed).\n if (!descriptor.joinKey && !descriptor.id)\n return clientError(`changeGroup: Insufficient information to identify compute group: ${JSON.stringify(descriptor)}.`);\n\n // Validate the properties in newDescriptor.\n for (const methodName in newDescriptor) {\n if (!Object.keys(changeCGPrototype.parameters).includes(methodName))\n return clientError(`changeGroup: Not allowed to change property ${methodName}. Can only change ${JSON.stringify(Object.keys(changeCGPrototype.parameters))}`);\n }\n\n // Translate joinSecret to joinHash.\n if (newDescriptor.joinSecret) {\n newDescriptor.joinHash = exports.calculateJoinHash(newDescriptor);\n delete newDescriptor.joinSecret;\n }\n\n descriptor = buildCGMessage(descriptor, 'changeGroup');\n debugging('computeGroups') && console.debug('change compute group client:', descriptor, newDescriptor);\n const { success, payload } = await exports.serviceConnection.send('changeGroup', { descriptor, newDescriptor });\n\n if (!success) throw new DCPError(`Cannot change compute group with ${cgId(descriptor)}:`, payload);\n if (payload && !payload.success) return reconstructServiceError(payload);\n\n return payload;\n};\n\n/**\n * Async function that deletes a compute group.\n * \n * The descriptor must have joinkey or id, where id:=opaqueId.\n * \n * Must either own the group or be ADMIN.\n * If not ADMIN, then the following config must be true:\n * dcpConfig.scheduler.services.computeGroups.usersCanDeleteGroups\n * \n * @param {cgAccessType} descriptor - Must contain joinKey or id (id:=opaqueId) \n * @returns {Promise<apiClientType>}\n * await computeGroup.deleteGroup({ joinKey: 'dcpDemo' });\n * await computeGroup.deleteGroup({ id: 'bYcYGQ3NOpFnP4FKs6IBQd' });\n * where the corresponding rows in table computeGroups have attributes\n * row1: joinKey:='dcpDemo',\n * row2: opaqueId:='bYcYGQ3NOpFnP4FKs6IBQd'\n */\nexports.deleteGroup = async function deleteGroup(descriptor)\n{\n if (!exports.serviceConnection)\n await openAndConnectServiceConn();\n\n // Verify descriptor has sufficient information to access a compute group (not guarenteed).\n if (!descriptor.joinKey && !descriptor.id)\n return clientError(`deleteGroup: Insufficient information to identify compute group: ${JSON.stringify(descriptor)}.`);\n\n descriptor = buildCGMessage(descriptor, 'deleteGroup');\n debugging('computeGroups') && console.debug('delete compute group client:', descriptor);\n const { success, payload } = await exports.serviceConnection.send('deleteGroup', { descriptor });\n\n if (!success) throw new DCPError(`Cannot delete compute group with ${cgId(descriptor)}:`, payload);\n if (payload && !payload.success) return reconstructServiceError(payload);\n\n return payload;\n};\n\n/**\n * Async function that adds a job to a specified compute group. \n * \n * Must be the owner of the job.\n *\n * Useful feedback is provided from this function, as it\n * will make its way back to the application developer, *after* they have made the\n * deployment fee micropayment.\n *\n * On the client side the access model in place is that if you know the (user/password)\n * joinKey+joinSecret/joinKey+joinHash/joinKey+joinHashHash/id+joinAddress,\n * you can add the job to the compute groups, where id:=opaqueId from table computeGroups.\n * On the service side the corresponding access model is\n * joinKey+joinHashHash/id+joinAddress .\n * Access is also allowed if the compute group owner is the connection peerAddress.\n * \n * Unless the compute group owner is the connection peerAddress, element of the descriptor array must contain\n * { joinKey, joinSecret }, { joinKey, joinHash } or { id, joinAddress }\n * where the value of id in { id, joinAddress } is the opaqueId attribute of the row in table computeGroups.\n *\n * @param {Address} job The address of the Job that will be added to the Compute Group.\n * @param {Array} computeGroups Array of descriptor objects for the compute groups. This descriptor\n * needs to contain enough information to authorize access to the\n * compute group. Properties may include:\n * - id (id:=opaqueId)\n * - joinKey\n * - joinSecret\n * - joinHash\n * - joinAddress\n * \n * Additional, either the joinKey or id MUST be specified so\n * that we can identify the compute group in question.\n *\n * All compute groups can have jobs submitted to them, provided either the joinKey\n * or the id are specified, and the message contains valid join permission and the \n * job is owned by the caller of addJobToGroups.\n *\n * FUTURE - after DCP-1910\n * keystore A keystore used to grant access to job deployment within this compute group.\n * This can be either the ownerKeystore or the joinAddress keystore when the\n * compute group is in deployAccessType='join' mode.\n * @returns {Promise<apiClientType>}\n * @access public\n * @example\n * await computeGroup.addJobToGroups('P+Y4IApeFQLrYS2W7MkVg7', \n * [ { joinKey: 'dcpDemo', joinSecret: 'theSecret' },\n * { joinKey: 'dcpDemo2', joinHash: 'eh1-e063976b20a92da97a27b9873465c6f2c9d6e4370befa86c8c1dd312c78befc2' }, \n * { id: 'bYcYGQ3NOpFnP4FKs6IBQd', joinAddress: 'c15053fc30d4bdf91e2e0bba79578f8b649e55ea' } ]);\n * where the corresponding rows in table computeGroups have attributes\n * row1: joinKey:='dcpDemo',\n * row2: joinKey:='dcpDemo2', joinHash:='eh1-e063976b20a92da97a27b9873465c6f2c9d6e4370befa86c8c1dd312c78befc2'\n * row3: opaqueId:='bYcYGQ3NOpFnP4FKs6IBQd', joinAddress:='c15053fc30d4bdf91e2e0bba79578f8b649e55ea' .\n */\nexports.addJobToGroups = async function addJobToGroups(job, computeGroups)\n{\n // ensure the service connection is hot before calling buildCGJoinMessage:\n await exports.keepAlive();\n\n const cgArray = [];\n for (const joinDescriptor of computeGroups)\n {\n // Verify joinDescriptor has sufficient information to authorize a compute group (not guarenteed).\n if (!hasSufficientJoinInfo(joinDescriptor))\n {\n console.error(`addJobToGroups: Insufficient information to authorize compute group: ${JSON.stringify(joinDescriptor)}.`);\n continue;\n }\n\n // Translate so that neither joinHash nor joinSecret goes across the wire.\n const message = buildCGJoinMessage(joinDescriptor, 'addJobToGroups');\n debugging('computeGroups') && console.debug(`addJobToGroups client: job ${job}, message`, message);\n\n cgArray.push(message);\n }\n\n const { success, payload } = await exports.serviceConnection.send('addJobToGroups', { job, cgArray });\n\n debugging('computeGroups') && console.debug('addJobToGroups payload', payload);\n\n if (!success) throw new DCPError(`Cannot add job ${job} to compute groups.`, payload);\n if (payload && !payload.success) return reconstructServiceError(payload);\n\n // If the server reported success but did not return a list of CGs (eg. v.4.2.5 server),\n // assume (and inform the client) we added all groups successfully\n return payload || computeGroups;\n};\n\n/**\n * Async function that lists all the Jobs in a Compute Group.\n * \n * The descriptor must have one of the properties joinkey, id (id:=opaqueId).\n * Must be the owner of the Compute Group to list jobs from it.\n * The job does not need to be owned.\n * \n * The descriptor is of the form { joinKey: 'dcpDemo' } or { id: 'bYcYGQ3NOpFnP4FKs6IBQd' }.\n * where 'bYcYGQ3NOpFnP4FKs6IBQd' is the opaqueId of the Compute Group.\n *\n * @param {cgAccessType} descriptor - Must have one of the properties joinKey, id (id:=opaqueId). Specifically\n * descriptor = { joinKey: 'dcpDemo' } or descriptor = { id: opaqueId }\n * @returns {Promise<apiClientType>}\n * @access public\n * @example\n * let listOfJobs1 = await computeGroup.listJobs({ joinKey: 'dcpDemo' });\n * let listOfJobs2 = await computeGroup.listJobs({ id: 'bYcYGQ3NOpFnP4FKs6IBQd' });\n * where the corresponding rows in table computeGroups have attributes\n * row1: joinKey:='dcpDemo'\n * row2: opaqueId:='bYcYGQ3NOpFnP4FKs6IBQd'\n */\nexports.listJobs = async function listJobs(descriptor)\n{\n if (!exports.serviceConnection)\n await openAndConnectServiceConn();\n\n // Verify descriptor has sufficient information to access a compute group (not guarenteed).\n if (!descriptor.joinKey && !descriptor.id)\n return clientError(`listJobs: Insufficient information to identify compute group: ${JSON.stringify(descriptor)}.`);\n\n descriptor = buildCGMessage(descriptor, 'listJobs');\n debugging('computeGroups') && console.debug('listJob client: descriptor', descriptor);\n const { success, payload } = await exports.serviceConnection.send('listJobs', { descriptor });\n\n if (!success) throw new DCPError(`Cannot list jobs for compute group with ${cgId(descriptor)}`, payload);\n if (payload && !payload.success) return reconstructServiceError(payload);\n\n return payload;\n};\n\n/**\n * Async function that removes a job from a Compute Group.\n * \n * The descriptor must have one of the properties joinkey, id (id:=opaqueId).\n * Must be the owner of the Compute Group to remove a job from it.\n * The job does not need to be owned.\n * \n * The descriptor is of the form { joinKey: 'dcpDemo' } or { id: 'bYcYGQ3NOpFnP4FKs6IBQd' }.\n * where 'bYcYGQ3NOpFnP4FKs6IBQd' is the opaqueId of the Compute Group.\n *\n * @param {Address} job - The address of the Job that will be added to the Compute Group.\n * @param {cgAccessType} descriptor - { joinKey: 'dcpDemo' } or { id: 'bYcYGQ3NOpFnP4FKs6IBQd' }\n * @returns {Promise<apiClientType>}\n * @access public\n * @example\n * await computeGroup.removeJob( 'P+Y4IApeFQLrYS2W7MkVg7', { joinKey: 'dcpDemo' });\n * await computeGroup.removeJob( 'P+Y4IApeFQLrYS2W7MkVg7', { id: 'bYcYGQ3NOpFnP4FKs6IBQd' });\n * where the corresponding rows in table computeGroups have attributes\n * row1: joinKey:='dcpDemo'\n * row2: opaqueId:='bYcYGQ3NOpFnP4FKs6IBQd'\n */\nexports.removeJob = async function removeJob(job, descriptor)\n{\n if (!exports.serviceConnection)\n await openAndConnectServiceConn();\n\n // Verify descriptor has sufficient information to access a compute group (not guarenteed).\n if (!descriptor.joinKey && !descriptor.id)\n return clientError(`removeJob: Insufficient information to identify compute group: ${JSON.stringify(descriptor)}.`);\n\n descriptor = buildCGMessage(descriptor, 'removeJob');\n debugging('computeGroups') && console.debug(`removeJob client: job ${job}, descriptor`, descriptor);\n const { success, payload } = await exports.serviceConnection.send('removeJob', { job, descriptor });\n\n if (!success) throw new DCPError(`Cannot remove job ${job} from compute group with ${cgId(descriptor)}`, payload);\n if (payload && !payload.success) return reconstructServiceError(payload);\n\n return payload;\n};\n\n/**\n * Async function that removes all jobs from a Compute Group.\n * \n * The descriptor must have one of the properties joinkey, id (id:=opaqueId).\n * Must be the owner of the Compute Group to remove jobs from it.\n * \n * The descriptor is of the form { joinKey: 'dcpDemo' } or { id: 'bYcYGQ3NOpFnP4FKs6IBQd' }.\n * where 'bYcYGQ3NOpFnP4FKs6IBQd' is the opaqueId of the Compute Group.\n *\n * @param {cgAccessType} descriptor - { joinKey: 'dcpDemo' } or { id: 'bYcYGQ3NOpFnP4FKs6IBQd' }\n * @returns {Promise<apiClientType>}\n * @access public\n * @example\n * await computeGroup.removeAllJobs({ joinKey: 'dcpDemo' });\n * await computeGroup.removeAllJobs({ id: 'bYcYGQ3NOpFnP4FKs6IBQd' });\n * where the corresponding rows in table computeGroups have attributes\n * row1: joinKey:='dcpDemo'\n * row2: opaqueId:='bYcYGQ3NOpFnP4FKs6IBQd'\n */\nexports.removeAllJobs = async function removeAllJobs(descriptor)\n{\n if (!exports.serviceConnection)\n await openAndConnectServiceConn();\n\n // Verify descriptor has sufficient information to access a compute group (not guarenteed).\n if (!descriptor.joinKey && !descriptor.id)\n return clientError(`removeAllJobs: Insufficient information to identify compute group: ${JSON.stringify(descriptor)}.`);\n\n descriptor = buildCGMessage(descriptor, 'removeAllJobs');\n debugging('computeGroups') && console.debug('removeAllJobs client: descriptor', descriptor);\n const { success, payload } = await exports.serviceConnection.send('removeAllJobs', { descriptor });\n\n if (!success) throw new DCPError(`Cannot remove all jobs from compute group with ${cgId(descriptor)}:`, payload);\n if (payload && !payload.success) return reconstructServiceError(payload);\n\n return payload;\n};\n\n/**\n * Async function that cancels the specified owned job.\n * \n * Must be the owner of the job.\n *\n * On the client side the access model in place is that if you know the (user/password)\n * joinKey+joinSecret/joinKey+joinHash/joinKey+joinHashHash/id+joinAddress,\n * you can cancel the job in the compute group, where id:=opaqueId from table computeGroups.\n * On the service side the corresponding access model is\n * joinKey+joinHashHash/id+joinAddress .\n * Access is also allowed if the compute group owner is the connection peerAddress.\n * \n * Unless the compute group owner is the connection peerAddress, the descriptor must contain\n * { joinKey, joinHashHash } or { id, joinAddress }\n * where the value of id in { id, joinAddress } is the opaqueId attribute of the row in table computeGroups.\n * \n * @param {Address} job - The address of the Job that will be added to the Compute Group.\n * @param {cgClientJoinType} joinDescriptor - Array of descriptor objects for the compute groups. This descriptor\n * needs to contain enough information to authorize access to the\n * compute group. Properties may include:\n * - id (id:=opaqueId)\n * - joinKey\n * - joinSecret\n * - joinHash\n * - joinAddress\n *\n * Additional, either the joinKey or id MUST be specified so\n * that we can identify the compute group in question.\n * @returns {Promise<apiClientType>}\n * @access public\n * @example\n * await computeGroup.cancelJob( 'P+Y4IApeFQLrYS2W7MkVg7', { joinKey: 'dcpDemo', joinHash: 'eh1-e063976b20a92da97a27b9873465c6f2c9d6e4370befa86c8c1dd312c78befc2' } );\n * await computeGroup.cancelJob( 'P+Y4IApeFQLrYS2W7MkVg7', { id: 'bYcYGQ3NOpFnP4FKs6IBQd', joinAddress: 'c15053fc30d4bdf91e2e0bba79578f8b649e55ea' });\n * where the corresponding rows in table computeGroups have attributes\n * row1: joinKey:='dcpDemo', joinHash:='eh1-e063976b20a92da97a27b9873465c6f2c9d6e4370befa86c8c1dd312c78befc2'\n * row2: opaqueId:='bYcYGQ3NOpFnP4FKs6IBQd', joinAddress:='c15053fc30d4bdf91e2e0bba79578f8b649e55ea' .\n */\nexports.cancelJob = async function cancelJob(job, joinDescriptor)\n{\n if (!exports.serviceConnection)\n await openAndConnectServiceConn();\n\n // Verify joinDescriptor has sufficient information to authorize a compute group (not guarenteed).\n if (!hasSufficientJoinInfo(joinDescriptor))\n return clientError(`cancelJob: Insufficient information to authorize compute group: ${JSON.stringify(joinDescriptor)}.`);\n\n // Translate so that neither joinHash nor joinSecret goes across the wire.\n joinDescriptor = buildCGJoinMessage(joinDescriptor, 'cancelJob');\n debugging('computeGroups') && console.debug(`cancelJob client: job ${job}, descriptor`, joinDescriptor);\n const { success, payload } = await exports.serviceConnection.send('cancelJob', { job, joinDescriptor });\n\n if (!success) throw new DCPError(`Cannot cancel job ${job} for compute group with ${cgId(joinDescriptor)}:`, payload);\n if (payload && !payload.success) return reconstructServiceError(payload);\n\n return payload;\n};\n\n/**\n * Async function that cancels the owned job in the Compute Group.\n * \n * On the client side the access model in place is that if you know the (user/password)\n * joinKey+joinSecret/joinKey+joinHash/joinKey+joinHashHash/id+joinAddress,\n * you can cancel the jobs in the compute group, where id:=opaqueId from table computeGroups.\n * On the service side the corresponding access model is\n * joinKey+joinHashHash/id+joinAddress .\n * Access is also allowed if the compute group owner is the connection peerAddress.\n * \n * Unless the compute group owner is the connection peerAddress, the descriptor must contain\n * { joinKey, joinHashHash } or { id, joinAddress }\n * where the value of id in { id, joinAddress } is the opaqueId attribute of the row in table computeGroups.\n * \n * @param {cgClientJoinType} joinDescriptor - Array of descriptor objects for the compute groups. This descriptor\n * needs to contain enough information to authorize access to the\n * compute group. Properties may include:\n * - id (id:=opaqueId)\n * - joinKey\n * - joinSecret\n * - joinHash\n * - joinAddress\n * \n * Additional, either the joinKey or id MUST be specified so\n * that we can identify the compute group in question.\n * @returns {Promise<apiClientType>}\n * @access public\n * @example\n * await computeGroup.cancelAllJobs( { joinKey: 'dcpDemo', joinHash: 'eh1-e063976b20a92da97a27b9873465c6f2c9d6e4370befa86c8c1dd312c78befc2' } );\n * await computeGroup.cancelAllJobs( { id: 'bYcYGQ3NOpFnP4FKs6IBQd', joinAddress: 'c15053fc30d4bdf91e2e0bba79578f8b649e55ea' });\n * where the corresponding rows in table computeGroups have attributes\n * row1: joinKey:='dcpDemo', joinHash:='eh1-e063976b20a92da97a27b9873465c6f2c9d6e4370befa86c8c1dd312c78befc2'\n * row2: opaqueId:='bYcYGQ3NOpFnP4FKs6IBQd', joinAddress:='c15053fc30d4bdf91e2e0bba79578f8b649e55ea' .\n */\nexports.cancelAllJobs = async function cancelAllJobs(joinDescriptor)\n{\n if (!exports.serviceConnection)\n await openAndConnectServiceConn();\n\n // Verify joinDescriptor has sufficient information to authorize a compute group (not guarenteed).\n if (!hasSufficientJoinInfo(joinDescriptor))\n return clientError(`cancelAllJobs: Insufficient information to authorize compute group: ${JSON.stringify(joinDescriptor)}.`);\n\n // Translate so that neither joinHash nor joinSecret goes across the wire.\n joinDescriptor = buildCGJoinMessage(joinDescriptor, 'cancelAllJobs');\n debugging('computeGroups') && console.debug('cancelAllJobs client: descriptor', joinDescriptor);\n const { success, payload } = await exports.serviceConnection.send('cancelAllJobs', { joinDescriptor });\n\n if (!success) throw new DCPError(`Cannot cancel owned jobs for compute group with ${cgId(joinDescriptor)}:`, payload);\n if (payload && !payload.success) return reconstructServiceError(payload);\n\n return payload;\n};\n\n/**\n * Calculate a joinHash for a compute group. This is an eh1- hash of the cg salt and \n * joinSecret components of a compute group description.\n *\n * @param {object} details an object containing the cg salt, which is\n * the joinKey if the compute group uses one;\n * otherwise it is the joinAddress. This object\n * may also contain the joinSecret.\n * @param {string} [joinSecret] the join secret -- plain text -- that is\n * the \"password\" for the compute group. If not\n * specified, we use details.joinSecret.\n */\nexports.calculateJoinHash = function computeGroups$calculateJoinHash(details, joinSecret)\n{\n if (typeof joinSecret === 'undefined')\n joinSecret = details.joinSecret;\n\n return hash.calculate(hash.eh1, `${details.joinKey || details.joinAddress} ${joinSecret}`);\n}\n\n\n//# sourceURL=webpack://dcp/./src/dcp-client/compute-groups/index.js?");
|
|
4187
|
+
eval("/**\n * @file Client facing module that implements Compute Groups API\n * @module dcp/compute-groups\n * @access public\n * @author Kayra E-A <kayra@kingsds.network>\n * Wes Garland <wes@kingsds.network>\n * Paul <paul@kingsds.network>\n * @date Sept 2020\n * February 2022\n * May 2022\n */\n\n\nconst protocolV4 = __webpack_require__(/*! dcp/protocol-v4 */ \"./src/protocol-v4/index.js\");\nconst wallet = __webpack_require__(/*! dcp/dcp-client/wallet */ \"./src/dcp-client/wallet/index.js\");\nconst hash = __webpack_require__(/*! ../../common/hash */ \"./src/common/hash.js\");\nconst { DCPError } = __webpack_require__(/*! ../../common/dcp-error */ \"./src/common/dcp-error.js\");\nconst debugging = (__webpack_require__(/*! dcp/debugging */ \"./src/debugging.js\").scope)('scheduler');\nconst { Address } = __webpack_require__(/*! dcp/dcp-client/wallet */ \"./src/dcp-client/wallet/index.js\");\nconst constants = __webpack_require__(/*! dcp/common/scheduler-constants */ \"./src/common/scheduler-constants.js\");\nconst { clientError, reconstructServiceError } = __webpack_require__(/*! dcp/utils */ \"./src/utils/index.js\");\n\n/** @typedef {import('dcp/utils').apiServiceType} apiServiceType */\n/** @typedef {import('dcp/utils').apiClientType} apiClientType */\n/** @typedef {string} opaqueId */\n\n/**\n * @typedef {object} cgAccessType\n * @property {opaqueId} [id]\n * @property {string} [joinKey]\n */\n\n/**\n * @typedef {object} cgClientJoinType\n * @property {opaqueId} [id]\n * @property {Address} [joinAddress]\n * @property {string} [joinKey]\n * @property {string} [joinSecret]\n * @property {string} [joinHash]\n */\n\n/**\n * @typedef {object} cgServiceJoinType\n * @property {opaqueId} [id]\n * @property {Address} [joinAddress]\n * @property {string} [joinKey]\n * @property {string} [joinHashHash]\n */\n\n/**\n * Establishes the client connection to the computeGroups microservice if it does not exist already from the default config.\n * \n * @returns {protocolV4.Connection}\n * @access public\n * @example\n * const result = await exports.serviceConnection.send('createGroup', {\n name: name,\n description: description,\n });\n */\n\nexports.serviceConnection = null;\n\n//\n// Reference counting pattern:\n// For every time addRef is called,\n// closeServiceConnection must eventually be called.\n// Reference counting allows multiple execs in a Promise.all .\n//\nvar refCount = 0;\nexports.addRef = function addRef() {\n refCount++;\n}\n\nconst openAndConnectServiceConn = async function openAndConnectServiceConn()\n{\n exports.serviceConnection = new protocolV4.Connection(dcpConfig.scheduler.services.computeGroups);\n exports.serviceConnection.on('close', openAndConnectServiceConn);\n await exports.serviceConnection.connect();\n refCount = 0; // Help with sanity.\n}\n\n/**\n * Resets the client connection to the computeGroups microservice.\n */\nexports.closeServiceConnection = async function closeServiceConnection() {\n if (refCount > 0) refCount--;\n if (exports.serviceConnection && refCount < 1)\n {\n exports.serviceConnection.off('close', openAndConnectServiceConn);\n exports.serviceConnection.close(null, true);\n refCount = 0; // Help with sanity.\n exports.serviceConnection = null;\n }\n};\n\n/**\n * (Used in jobs/index.js)\n * KeepAlive for the service connection to compute groups.\n */\nexports.keepAlive = async function keepAlive() {\n if (!exports.serviceConnection)\n await openAndConnectServiceConn();\n\n exports.serviceConnection.keepalive().catch(err => console.error('Warning: keepalive failed for compute groups service', err));\n}\n\n/**\n * Checks whether descriptor corresponds to the public compute group from the scheduler constants.\n */\nexports.isPublicComputeGroup = function isPublicComputeGroup(descriptor) {\n return descriptor.id === constants.computeGroups.public.id\n && descriptor.opaqueId === constants.computeGroups.public.opaqueId;\n};\n\n/**\n * Returns a compute group identification snippet for diagnostic messages,\n * @param {object} descriptor - Must have one of the properties joinKey, id (id:=opaqueId). Specifically\n * descriptor = { joinKey: 'dcpDemo' } or descriptor = { id: 'bYcYGQ3NOpFnP4FKs6IBQd' },\n * where the corresponding row in table computeGroups have attributes\n * joinKey:='dcpDemo' or opaqueId:='bYcYGQ3NOpFnP4FKs6IBQd' .\n * @returns {string}\n */\nfunction cgId(descriptor) {\n return (descriptor.joinKey) ? `joinKey ${descriptor.joinKey}` : `id ${descriptor.id}`;\n}\n\n/**\n * Verify sufficient information in descriptor to access a compute group.\n * Emit diagnostics about unnecessary information.\n * @param {cgAccessType} descriptor \n * @param {string} methodName \n */\nfunction validateCGDescriptor(descriptor, methodName) {\n for (const prop in descriptor) {\n if ([ 'id', 'joinKey' ].includes(prop)) continue;\n if ([ 'joinAddress', 'joinHash', 'joinSecret' ].includes(prop))\n console.warn(`It is not necessary to specify '${prop}' in the descriptor ${JSON.stringify(descriptor)} when calling ${methodName}`);\n else\n console.error(`Do not specify '${prop}' in the descriptor ${JSON.stringify(descriptor)} when calling ${methodName}`);\n }\n}\n\n/**\n * Verify sufficient information in descriptor to authorize a compute group.\n * Emit diagnostics about unnecessary information.\n * @param {cgClientJoinType} joinDescriptor \n * @param {string} methodName \n */\nfunction validateCGJoinDescriptor(joinDescriptor, methodName) {\n for (const prop in joinDescriptor) {\n if ([ 'id', 'joinKey', 'joinSecret', 'joinHash', 'joinAddress' ].includes(prop)) continue;\n console.error(`Do not specify '${prop}' in the descriptor ${JSON.stringify(joinDescriptor)} when calling ${methodName}`);\n }\n}\n\n/**\n * Build message to go across the wire.\n * Verify sufficient information in descriptor to access a compute group.\n * Emit diagnostics about unnecessary information.\n * @param {cgAccessType} descriptor\n * @param {string} methodName\n * @returns {cgAccessType}\n */\nfunction buildCGMessage(descriptor, methodName)\n{\n if (exports.isPublicComputeGroup(descriptor)) return descriptor;\n\n const message = {};\n // Construct message.joinKey xor message.id .\n if (descriptor.joinKey) message.joinKey = descriptor.joinKey;\n else if (descriptor.id) message.id = descriptor.id; // id:=opaqueId\n\n debugging('computeGroups') && console.debug(`${methodName}:buildCGMessage: descriptor`, descriptor, 'message', message);\n\n validateCGDescriptor(descriptor, methodName);\n\n return message;\n}\n\n/**\n * Build message so that joinHash, joinSecret, opaqueId do not go across the wire.\n * Verify sufficient information in descriptor to authorize a compute group.\n * Emit diagnostics about unnecessary information.\n * @param {cgClientJoinType} descriptor\n * @param {string} methodName\n * @returns {cgServiceJoinType}\n */\nfunction buildCGJoinMessage(descriptor, methodName)\n{\n if (exports.isPublicComputeGroup(descriptor)) return descriptor;\n\n const message = {};\n // Construct message.joinKey xor message.id .\n if (descriptor.joinKey) message.joinKey = descriptor.joinKey;\n else if (descriptor.id) message.id = descriptor.id; // id:=opaqueId\n // Construct message.joinAddress .\n if (descriptor.joinAddress) message.joinAddress = descriptor.joinAddress;\n\n debugging('computeGroups') && console.debug(`${methodName}:buildCGJoinMessage: descriptor`, descriptor, 'message', message);\n\n validateCGJoinDescriptor(descriptor, methodName);\n\n // Construct message.joinHashHash .\n if (descriptor.joinSecret) message.joinHashHash = hash.calculate(hash.eh1, exports.calculateJoinHash(descriptor), exports.serviceConnection.dcpsid);\n if (descriptor.joinHash) message.joinHashHash = hash.calculate(hash.eh1, descriptor.joinHash, exports.serviceConnection.dcpsid);\n\n return message;\n}\n\nfunction hasSufficientJoinInfo(joinDescriptor) {\n // Verify joinDescriptor has sufficient information to authorize a compute group (not guarenteed).\n return (joinDescriptor.joinKey && (joinDescriptor.joinSecret || joinDescriptor.joinHash))\n || (joinDescriptor.id && joinDescriptor.joinAddress)\n || exports.isPublicComputeGroup(joinDescriptor);\n}\n\nconst newCGPrototype = { type: 'object',\n parameters: {\n // name: { type: 'string', default: undefined }, /* name of group (length <= 255) */\n // description: { type: 'string', default: undefined }, /* description of group (length <= 255) */\n // id: { type: 'string', default: undefined }, /* opaqueId, the unique identifier of the compute group; nanoid (length === 22) */\n // joinKey: { type: 'string', default: undefined }, /* basically the login (length <= 255) */\n // joinSecret: { type: 'string', default: undefined }, /* basically the password (length <= 255) */\n // joinHash: { type: 'string', default: undefined }, /* basically the password, the joinSecret seeded & hashed */\n // joinAddress: { type: Address, default: undefined }, /* signature gives alternative to login/password */\n\n commissionRate: { type: 'BigNumber', default: undefined }, /* commission, see DCP-1889 */\n deployFee: { type: 'BigNumber', default: undefined }, /* number of DCC to take for every deployment */\n deployAccess: { type: 'string', default: undefined }, /* can be \"owner\"|\"join\" (dcp-1910) */\n addJobFee: { type: 'BigNumber', default: undefined }, /* fee required each time a job joins a compute group */\n maxTotalPayment: { type: 'BigNumber', default: undefined }, /* limit on maximum job payment, NULL => Infinity */\n\n /* Administrative limits on group. NULL => Infinity: Should all be integers or undefined. */\n maxConcurrentJobs: { type: 'number', default: undefined },\n maxConcurrentWorkers: { type: 'number', default: undefined },\n maxConcurrentSandboxes: { type: 'number', default: undefined },\n maxConcurrentCPUs: { type: 'number', default: undefined },\n maxConcurrentGPUs: { type: 'number', default: undefined },\n maxConcurrentEscrow: { type: 'BigNumber', default: undefined },\n },\n};\n\n/**\n * Async function that creates a new Compute Group.\n *\n * The joinDescriptor is of the form { joinKey, joinSecret }, { joinKey, joinHash } or { id, joinAddress }.\n * where id will correspond to the attribute opaqueId in the new row in the computeGroups table.\n *\n * This function can only be called with ADMIN permission.\n * Properties not appearing in newCGPrototype.parameters are not allowed in otherProperties.\n *\n * @param {cgClientJoinType} joinDescriptor - Must have properly defined { joinKey, joinSecret }, { joinKey, joinHash }\n * or { id, joinAddress }, where id will correspond to the attribute opaqueId\n * in the new row in the computeGroups table.\n * @param {string} [name] - The name of the compute group.\n * @param {string} [description] - The description of the compute group.\n * @param {object} [otherProperties] - The 5 attributes of table computeGroup related to commissions and fees.\n * commissionRate: notNull(zFinNum),// commission, see DCP-1889\n * deployFee: notNull(zFinNum),// number of DCC to take for every deployment\n * deployAccess: string, // can be \"owner\"|\"join\" (dcp-1910)\n * addJobFee: notNull(zFinNum),// fee required each time a job joins a compute group\n * maxTotalPayment: finNum, // limit on maximum job payment, NULL => Infinity\n * And the 6 attributes of table computeGroup related to limits.\n * maxConcurrentJobs: integer,\n * maxConcurrentWorkers: integer,\n * maxConcurrentSandboxes: integer,\n * maxConcurrentCPUs: integer,\n * maxConcurrentGPUs: integer,\n * maxConcurrentEscrow: finNum,\n * @returns {Promise<apiClientType>} - { success, payload: computeGroup.id }\n * @access public\n * @example\n * await computeGroup.createGroup({ joinKey: 'dcpDemo', joinSecret: 'theSecret' }, 'myCGName', 'myCGDescription', { deployFee: 0.00015 });\n * await computeGroup.createGroup({ joinKey: 'dcpDemo2', joinHash: 'eh1-e063976b20a92da97a27b9873465c6f2c9d6e4370befa86c8c1dd312c78befc2' });\n * await computeGroup.createGroup({ id: 'bYcYGQ3NOpFnP4FKs6IBQd', joinAddress: 'c15053fc30d4bdf91e2e0bba79578f8b649e55ea' });\n * where the corresponding rows in table computeGroups have attributes\n * row1: joinKey:='dcpDemo',\n * row2: joinKey:='dcpDemo2', joinHash:='eh1-e063976b20a92da97a27b9873465c6f2c9d6e4370befa86c8c1dd312c78befc2'\n * row3: opaqueId:='bYcYGQ3NOpFnP4FKs6IBQd', joinAddress:='c15053fc30d4bdf91e2e0bba79578f8b649e55ea' .\n */\nexports.createGroup = async function createGroup(joinDescriptor, name, description, otherProperties)\n{\n if (!exports.serviceConnection)\n await openAndConnectServiceConn();\n\n validateCGJoinDescriptor(joinDescriptor, 'createGroup');\n\n // Verify joinDescriptor has sufficient information to authorize a compute group (not guarenteed).\n if (!hasSufficientJoinInfo(joinDescriptor))\n return clientError(`createGroup: Insufficient information to authorize compute group: ${JSON.stringify(joinDescriptor)}.`);\n\n // Validate the properties in otherProperties.\n for (const methodName in otherProperties) {\n if (!Object.keys(newCGPrototype.parameters).includes(methodName))\n return clientError(`createGroup: Property ${methodName} cannot be speicfied in otherProperties. Can only specify ${JSON.stringify(Object.keys(newCGPrototype.parameters))}`);\n }\n\n // Translate joinSecret to joinHash.\n if (joinDescriptor.joinSecret) {\n joinDescriptor.joinHash = exports.calculateJoinHash(joinDescriptor);\n delete joinDescriptor.joinSecret;\n }\n\n if (otherProperties && (otherProperties.commissionRate < 0 || otherProperties.commissionRate >= 1))\n return clientError(`client-createGroup: commissionRate ${otherProperties.commissionRate} must be between 0 and 1 (0 <= commissionRate < 1).`);\n\n debugging('computeGroups') && console.debug('client-createGroup: input:', joinDescriptor, name, description, otherProperties);\n\n const { success, payload } = await exports.serviceConnection.send('createGroup', { joinDescriptor, name, description, otherProperties });\n\n if (!success) return clientError(`Cannot create new compute group, with ${cgId(joinDescriptor)}.`);\n if (payload && !payload.success) return reconstructServiceError(payload);\n\n debugging('computeGroups') && console.debug('client-createGroup: payload', payload);\n\n return payload;\n};\n\nconst changeCGPrototype = { type: 'object',\n parameters: {\n name: { type: 'string', default: undefined }, /* name of group (length <= 255) */\n description: { type: 'string', default: undefined }, /* description of group (length <= 255) */\n joinHash: { type: 'string', default: undefined }, /* basically the password, seeded & hashed (length <= 255) */\n joinAddress: { type: Address, default: undefined }, /* signature gives alternative to login/password */\n\n commissionRate: { type: 'BigNumber', default: undefined }, /* commission, see DCP-1889 */\n deployFee: { type: 'BigNumber', default: undefined }, /* number of DCC to take for every deployment */\n deployAccess: { type: 'string', default: undefined }, /* can be \"owner\"|\"join\" (dcp-1910) */\n addJobFee: { type: 'BigNumber', default: undefined }, /* fee required each time a job joins a compute group */\n maxTotalPayment: { type: 'BigNumber', default: undefined }, /* limit on maximum job payment, NULL => Infinity */\n\n /* Administrative limits on group. NULL => Infinity: Should all be integers or undefined. */\n maxConcurrentJobs: { type: 'number', default: undefined },\n maxConcurrentWorkers: { type: 'number', default: undefined },\n maxConcurrentSandboxes: { type: 'number', default: undefined },\n maxConcurrentCPUs: { type: 'number', default: undefined },\n maxConcurrentGPUs: { type: 'number', default: undefined },\n maxConcurrentEscrow: { type: 'BigNumber', default: undefined },\n },\n};\n\n/**\n * Async function that changes a new Compute Group.\n * \n * The parameter newDescriptor contains the new property values,\n * and the properties that are allowed to be changed appear in changeCGPrototype.parameters.\n * \n * The descriptor must have joinKey or id, where id:=opaqueId.\n * Must own the compute group or be ADMIN to use changeGroup.\n * \n * @param {cgAccessType} descriptor - Must have joinkey or id, where id:=opaqueId.\n * @param {object} newDescriptor - Properties not appearing in changeCGPrototype.parameters are not allowed.\n * @returns {Promise<apiClientType>}\n * await computeGroup.changeGroup({ joinKey: 'dcpDemo' }, { joinSecret: 'myNewPasswrd' });\n * await computeGroup.changeGroup({ id: 'bYcYGQ3NOpFnP4FKs6IBQd' }, { name: 'myNewName', deployFee: 0.0001 });\n * where the corresponding rows in table computeGroups have attributes\n * row1: joinKey:='dcpDemo',\n * row2: opaqueId:='bYcYGQ3NOpFnP4FKs6IBQd'\n */\nexports.changeGroup = async function changeGroup(descriptor, newDescriptor)\n{\n if (!exports.serviceConnection)\n await openAndConnectServiceConn();\n\n // Verify descriptor has sufficient information to access a compute group (not guarenteed).\n if (!descriptor.joinKey && !descriptor.id)\n return clientError(`changeGroup: Insufficient information to identify compute group: ${JSON.stringify(descriptor)}.`);\n\n // Validate the properties in newDescriptor.\n for (const methodName in newDescriptor) {\n if (!Object.keys(changeCGPrototype.parameters).includes(methodName))\n return clientError(`changeGroup: Not allowed to change property ${methodName}. Can only change ${JSON.stringify(Object.keys(changeCGPrototype.parameters))}`);\n }\n\n // Translate joinSecret to joinHash.\n if (newDescriptor.joinSecret) {\n newDescriptor.joinHash = exports.calculateJoinHash(newDescriptor);\n delete newDescriptor.joinSecret;\n }\n\n descriptor = buildCGMessage(descriptor, 'changeGroup');\n debugging('computeGroups') && console.debug('change compute group client:', descriptor, newDescriptor);\n const { success, payload } = await exports.serviceConnection.send('changeGroup', { descriptor, newDescriptor });\n\n if (!success) throw new DCPError(`Cannot change compute group with ${cgId(descriptor)}:`, payload);\n if (payload && !payload.success) return reconstructServiceError(payload);\n\n return payload;\n};\n\n/**\n * Async function that deletes a compute group.\n * \n * The descriptor must have joinkey or id, where id:=opaqueId.\n * \n * Must either own the group or be ADMIN.\n * If not ADMIN, then the following config must be true:\n * dcpConfig.scheduler.services.computeGroups.usersCanDeleteGroups\n * \n * @param {cgAccessType} descriptor - Must contain joinKey or id (id:=opaqueId) \n * @returns {Promise<apiClientType>}\n * await computeGroup.deleteGroup({ joinKey: 'dcpDemo' });\n * await computeGroup.deleteGroup({ id: 'bYcYGQ3NOpFnP4FKs6IBQd' });\n * where the corresponding rows in table computeGroups have attributes\n * row1: joinKey:='dcpDemo',\n * row2: opaqueId:='bYcYGQ3NOpFnP4FKs6IBQd'\n */\nexports.deleteGroup = async function deleteGroup(descriptor)\n{\n if (!exports.serviceConnection)\n await openAndConnectServiceConn();\n\n // Verify descriptor has sufficient information to access a compute group (not guarenteed).\n if (!descriptor.joinKey && !descriptor.id)\n return clientError(`deleteGroup: Insufficient information to identify compute group: ${JSON.stringify(descriptor)}.`);\n\n descriptor = buildCGMessage(descriptor, 'deleteGroup');\n debugging('computeGroups') && console.debug('delete compute group client:', descriptor);\n const { success, payload } = await exports.serviceConnection.send('deleteGroup', { descriptor });\n\n if (!success) throw new DCPError(`Cannot delete compute group with ${cgId(descriptor)}:`, payload);\n if (payload && !payload.success) return reconstructServiceError(payload);\n\n return payload;\n};\n\n/**\n * Async function that adds a job to a specified compute group. \n * \n * Must be the owner of the job.\n *\n * Useful feedback is provided from this function, as it\n * will make its way back to the application developer, *after* they have made the\n * deployment fee micropayment.\n *\n * On the client side the access model in place is that if you know the (user/password)\n * joinKey+joinSecret/joinKey+joinHash/joinKey+joinHashHash/id+joinAddress,\n * you can add the job to the compute groups, where id:=opaqueId from table computeGroups.\n * On the service side the corresponding access model is\n * joinKey+joinHashHash/id+joinAddress .\n * Access is also allowed if the compute group owner is the connection peerAddress.\n * \n * Unless the compute group owner is the connection peerAddress, element of the descriptor array must contain\n * { joinKey, joinSecret }, { joinKey, joinHash } or { id, joinAddress }\n * where the value of id in { id, joinAddress } is the opaqueId attribute of the row in table computeGroups.\n *\n * @param {Address} job The address of the Job that will be added to the Compute Group.\n * @param {Array} computeGroups Array of descriptor objects for the compute groups. This descriptor\n * needs to contain enough information to authorize access to the\n * compute group. Properties may include:\n * - id (id:=opaqueId)\n * - joinKey\n * - joinSecret\n * - joinHash\n * - joinAddress\n * \n * Additional, either the joinKey or id MUST be specified so\n * that we can identify the compute group in question.\n *\n * All compute groups can have jobs submitted to them, provided either the joinKey\n * or the id are specified, and the message contains valid join permission and the \n * job is owned by the caller of addJobToGroups.\n *\n * FUTURE - after DCP-1910\n * keystore A keystore used to grant access to job deployment within this compute group.\n * This can be either the ownerKeystore or the joinAddress keystore when the\n * compute group is in deployAccessType='join' mode.\n * @returns {Promise<apiClientType>}\n * @access public\n * @example\n * await computeGroup.addJobToGroups('P+Y4IApeFQLrYS2W7MkVg7', \n * [ { joinKey: 'dcpDemo', joinSecret: 'theSecret' },\n * { joinKey: 'dcpDemo2', joinHash: 'eh1-e063976b20a92da97a27b9873465c6f2c9d6e4370befa86c8c1dd312c78befc2' }, \n * { id: 'bYcYGQ3NOpFnP4FKs6IBQd', joinAddress: 'c15053fc30d4bdf91e2e0bba79578f8b649e55ea' } ]);\n * where the corresponding rows in table computeGroups have attributes\n * row1: joinKey:='dcpDemo',\n * row2: joinKey:='dcpDemo2', joinHash:='eh1-e063976b20a92da97a27b9873465c6f2c9d6e4370befa86c8c1dd312c78befc2'\n * row3: opaqueId:='bYcYGQ3NOpFnP4FKs6IBQd', joinAddress:='c15053fc30d4bdf91e2e0bba79578f8b649e55ea' .\n */\nexports.addJobToGroups = async function addJobToGroups(job, computeGroups)\n{\n // ensure the service connection is hot before calling buildCGJoinMessage:\n await exports.keepAlive();\n\n const cgArray = [];\n for (const joinDescriptor of computeGroups)\n {\n // Verify joinDescriptor has sufficient information to authorize a compute group (not guarenteed).\n if (!hasSufficientJoinInfo(joinDescriptor))\n {\n console.error(`addJobToGroups: Insufficient information to authorize compute group: ${JSON.stringify(joinDescriptor)}.`);\n continue;\n }\n\n // Translate so that neither joinHash nor joinSecret goes across the wire.\n const message = buildCGJoinMessage(joinDescriptor, 'addJobToGroups');\n debugging('computeGroups') && console.debug(`addJobToGroups client: job ${job}, message`, message);\n\n cgArray.push(message);\n }\n\n const { success, payload } = await exports.serviceConnection.send('addJobToGroups', { job, cgArray });\n\n debugging('computeGroups') && console.debug('addJobToGroups payload', payload);\n\n if (!success) throw new DCPError(`Cannot add job ${job} to compute groups.`, payload);\n if (payload && !payload.success) return reconstructServiceError(payload);\n\n // If the server reported success but did not return a list of CGs (eg. v.4.2.5 server),\n // assume (and inform the client) we added all groups successfully\n return payload || computeGroups;\n};\n\n/**\n * Async function that lists all the Jobs in a Compute Group.\n * \n * The descriptor must have one of the properties joinkey, id (id:=opaqueId).\n * Must be the owner of the Compute Group to list jobs from it.\n * The job does not need to be owned.\n * \n * The descriptor is of the form { joinKey: 'dcpDemo' } or { id: 'bYcYGQ3NOpFnP4FKs6IBQd' }.\n * where 'bYcYGQ3NOpFnP4FKs6IBQd' is the opaqueId of the Compute Group.\n *\n * @param {cgAccessType} descriptor - Must have one of the properties joinKey, id (id:=opaqueId). Specifically\n * descriptor = { joinKey: 'dcpDemo' } or descriptor = { id: opaqueId }\n * @returns {Promise<apiClientType>}\n * @access public\n * @example\n * let listOfJobs1 = await computeGroup.listJobs({ joinKey: 'dcpDemo' });\n * let listOfJobs2 = await computeGroup.listJobs({ id: 'bYcYGQ3NOpFnP4FKs6IBQd' });\n * where the corresponding rows in table computeGroups have attributes\n * row1: joinKey:='dcpDemo'\n * row2: opaqueId:='bYcYGQ3NOpFnP4FKs6IBQd'\n */\nexports.listJobs = async function listJobs(descriptor)\n{\n if (!exports.serviceConnection)\n await openAndConnectServiceConn();\n\n // Verify descriptor has sufficient information to access a compute group (not guarenteed).\n if (!descriptor.joinKey && !descriptor.id)\n return clientError(`listJobs: Insufficient information to identify compute group: ${JSON.stringify(descriptor)}.`);\n\n descriptor = buildCGMessage(descriptor, 'listJobs');\n debugging('computeGroups') && console.debug('listJob client: descriptor', descriptor);\n const { success, payload } = await exports.serviceConnection.send('listJobs', { descriptor });\n\n if (!success) throw new DCPError(`Cannot list jobs for compute group with ${cgId(descriptor)}`, payload);\n if (payload && !payload.success) return reconstructServiceError(payload);\n\n return payload;\n};\n\n/**\n * Async function that removes a job from a Compute Group.\n * \n * The descriptor must have one of the properties joinkey, id (id:=opaqueId).\n * Must be the owner of the Compute Group to remove a job from it.\n * The job does not need to be owned.\n * \n * The descriptor is of the form { joinKey: 'dcpDemo' } or { id: 'bYcYGQ3NOpFnP4FKs6IBQd' }.\n * where 'bYcYGQ3NOpFnP4FKs6IBQd' is the opaqueId of the Compute Group.\n *\n * @param {Address} job - The address of the Job that will be added to the Compute Group.\n * @param {cgAccessType} descriptor - { joinKey: 'dcpDemo' } or { id: 'bYcYGQ3NOpFnP4FKs6IBQd' }\n * @returns {Promise<apiClientType>}\n * @access public\n * @example\n * await computeGroup.removeJob( 'P+Y4IApeFQLrYS2W7MkVg7', { joinKey: 'dcpDemo' });\n * await computeGroup.removeJob( 'P+Y4IApeFQLrYS2W7MkVg7', { id: 'bYcYGQ3NOpFnP4FKs6IBQd' });\n * where the corresponding rows in table computeGroups have attributes\n * row1: joinKey:='dcpDemo'\n * row2: opaqueId:='bYcYGQ3NOpFnP4FKs6IBQd'\n */\nexports.removeJob = async function removeJob(job, descriptor)\n{\n if (!exports.serviceConnection)\n await openAndConnectServiceConn();\n\n // Verify descriptor has sufficient information to access a compute group (not guarenteed).\n if (!descriptor.joinKey && !descriptor.id)\n return clientError(`removeJob: Insufficient information to identify compute group: ${JSON.stringify(descriptor)}.`);\n\n descriptor = buildCGMessage(descriptor, 'removeJob');\n debugging('computeGroups') && console.debug(`removeJob client: job ${job}, descriptor`, descriptor);\n const { success, payload } = await exports.serviceConnection.send('removeJob', { job, descriptor });\n\n if (!success) throw new DCPError(`Cannot remove job ${job} from compute group with ${cgId(descriptor)}`, payload);\n if (payload && !payload.success) return reconstructServiceError(payload);\n\n return payload;\n};\n\n/**\n * Async function that removes all jobs from a Compute Group.\n * \n * The descriptor must have one of the properties joinkey, id (id:=opaqueId).\n * Must be the owner of the Compute Group to remove jobs from it.\n * \n * The descriptor is of the form { joinKey: 'dcpDemo' } or { id: 'bYcYGQ3NOpFnP4FKs6IBQd' }.\n * where 'bYcYGQ3NOpFnP4FKs6IBQd' is the opaqueId of the Compute Group.\n *\n * @param {cgAccessType} descriptor - { joinKey: 'dcpDemo' } or { id: 'bYcYGQ3NOpFnP4FKs6IBQd' }\n * @returns {Promise<apiClientType>}\n * @access public\n * @example\n * await computeGroup.removeAllJobs({ joinKey: 'dcpDemo' });\n * await computeGroup.removeAllJobs({ id: 'bYcYGQ3NOpFnP4FKs6IBQd' });\n * where the corresponding rows in table computeGroups have attributes\n * row1: joinKey:='dcpDemo'\n * row2: opaqueId:='bYcYGQ3NOpFnP4FKs6IBQd'\n */\nexports.removeAllJobs = async function removeAllJobs(descriptor)\n{\n if (!exports.serviceConnection)\n await openAndConnectServiceConn();\n\n // Verify descriptor has sufficient information to access a compute group (not guarenteed).\n if (!descriptor.joinKey && !descriptor.id)\n return clientError(`removeAllJobs: Insufficient information to identify compute group: ${JSON.stringify(descriptor)}.`);\n\n descriptor = buildCGMessage(descriptor, 'removeAllJobs');\n debugging('computeGroups') && console.debug('removeAllJobs client: descriptor', descriptor);\n const { success, payload } = await exports.serviceConnection.send('removeAllJobs', { descriptor });\n\n if (!success) throw new DCPError(`Cannot remove all jobs from compute group with ${cgId(descriptor)}:`, payload);\n if (payload && !payload.success) return reconstructServiceError(payload);\n\n return payload;\n};\n\n/**\n * Calculate a joinHash for a compute group. This is an eh1- hash of the cg salt and \n * joinSecret components of a compute group description.\n *\n * @param {object} details an object containing the cg salt, which is\n * the joinKey if the compute group uses one;\n * otherwise it is the joinAddress. This object\n * may also contain the joinSecret.\n * @param {string} [joinSecret] the join secret -- plain text -- that is\n * the \"password\" for the compute group. If not\n * specified, we use details.joinSecret.\n */\nexports.calculateJoinHash = function computeGroups$calculateJoinHash(details, joinSecret)\n{\n if (typeof joinSecret === 'undefined')\n joinSecret = details.joinSecret;\n\n return hash.calculate(hash.eh1, `${details.joinKey || details.joinAddress} ${joinSecret}`);\n}\n\n\n//# sourceURL=webpack://dcp/./src/dcp-client/compute-groups/index.js?");
|
|
4115
4188
|
|
|
4116
4189
|
/***/ }),
|
|
4117
4190
|
|
|
@@ -4131,7 +4204,7 @@ eval("/**\n * @file Module that implements Compute API\n * @module dcp/comput
|
|
|
4131
4204
|
\*********************************/
|
|
4132
4205
|
/***/ ((module, exports, __webpack_require__) => {
|
|
4133
4206
|
|
|
4134
|
-
eval("/* module decorator */ module = __webpack_require__.nmd(module);\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 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 /* Allow client programs to use modules which happen to be in the bundle anyhow */\n let conveniencePeers = {\n 'ethereumjs-wallet': (__webpack_require__(/*! ./wallet/keystore */ \"./src/dcp-client/wallet/keystore.js\")._internalEth.wallet),\n 'ethereumjs-util': (__webpack_require__(/*! ./wallet/keystore */ \"./src/dcp-client/wallet/keystore.js\")._internalEth.util),\n 'socket.io-client': __webpack_require__(/*! socket.io-client */ \"./node_modules/socket.io-client/build/cjs/index.js\"),\n 'bignumber.js': __webpack_require__(/*! bignumber.js */ \"./node_modules/bignumber.js/bignumber.js\"),\n 'semver': __webpack_require__(/*! semver */ \"./node_modules/semver/semver.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\":\"
|
|
4207
|
+
eval("/* module decorator */ module = __webpack_require__.nmd(module);\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 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 /* Allow client programs to use modules which happen to be in the bundle anyhow */\n let conveniencePeers = {\n 'ethereumjs-wallet': (__webpack_require__(/*! ./wallet/keystore */ \"./src/dcp-client/wallet/keystore.js\")._internalEth.wallet),\n 'ethereumjs-util': (__webpack_require__(/*! ./wallet/keystore */ \"./src/dcp-client/wallet/keystore.js\")._internalEth.util),\n 'socket.io-client': __webpack_require__(/*! socket.io-client */ \"./node_modules/socket.io-client/build/cjs/index.js\"),\n 'bignumber.js': __webpack_require__(/*! bignumber.js */ \"./node_modules/bignumber.js/bignumber.js\"),\n 'semver': __webpack_require__(/*! semver */ \"./node_modules/semver/semver.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\":\"0bf54dbe88ee11e84b28e62b73cbd154d06967ea\",\"branch\":\"release\",\"dcpClient\":{\"version\":\"4.2.22\",\"from\":\"git+ssh://git@gitlab.com/Distributed-Compute-Protocol/dcp-client.git#prod-20221115\",\"resolved\":\"git+ssh://git@gitlab.com/Distributed-Compute-Protocol/dcp-client.git#295f18d25636449034433ddd3789070a5406c98e\"},\"built\":\"Mon Nov 21 2022 11:41:21 GMT-0500 (Eastern Standard Time)\",\"config\":{\"generated\":\"Mon 21 Nov 2022 11:41:19 AM EST by erose on lorge\",\"build\":\"debug\"},\"webpack\":\"5.74.0\",\"node\":\"v14.21.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 'legacy-modal': (__webpack_require__(/*! ../../portal/www/js/modal */ \"./portal/www/js/modal.js\").Modal),\n 'eth': __webpack_require__(/*! ./wallet/eth */ \"./src/dcp-client/wallet/eth.js\"),\n 'serialize': __webpack_require__(/*! ../utils/serialize */ \"./src/utils/serialize.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 }, conveniencePeers, 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 if (typeof BigInt !== 'undefined')\n modules['standard-objects']['BigInt'] === BigInt;\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 })\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?");
|
|
4135
4208
|
|
|
4136
4209
|
/***/ }),
|
|
4137
4210
|
|
|
@@ -4141,7 +4214,7 @@ eval("/* module decorator */ module = __webpack_require__.nmd(module);\n/**\n *
|
|
|
4141
4214
|
\**************************************/
|
|
4142
4215
|
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
|
4143
4216
|
|
|
4144
|
-
eval("/**\n * @file job-values.js\n * Utility code related to encoding or decode client-supplied values that are used in the\n * worker (or vice-versa), such as elements of the input set, output set, and work function\n * arguments.\n * @author Wes Garland, wes@kingsds.network\n * @date Feb 2022\n */\n\nconst { DcpURL } = __webpack_require__(/*! dcp/common/dcp-url */ \"./src/common/dcp-url.js\");\nconst
|
|
4217
|
+
eval("/**\n * @file job-values.js\n * Utility code related to encoding or decode client-supplied values that are used in the\n * worker (or vice-versa), such as elements of the input set, output set, and work function\n * arguments.\n * @author Wes Garland, wes@kingsds.network\n * @date Feb 2022\n */\n\nconst { DcpURL } = __webpack_require__(/*! dcp/common/dcp-url */ \"./src/common/dcp-url.js\");\nconst debugging = (__webpack_require__(/*! dcp/debugging */ \"./src/debugging.js\").scope)('dcp-client');\nconst KVIN = __webpack_require__(/*! kvin */ \"./node_modules/kvin/kvin.js\");\n\n/** @todo - move submodules into a dir; refactor to use this module only */\nObject.assign({}, __webpack_require__(/*! ./remote-data-set */ \"./src/dcp-client/remote-data-set.js\"));\nObject.assign({}, __webpack_require__(/*! ./remote-data-pattern */ \"./src/dcp-client/remote-data-pattern.js\"));\n\n/**\n * Represent a single datum at a \"remote\" location.\n *\n * @constructor RemoteValue\n * @extends DcpURL\n */\nexports.RemoteValue = function remoteDataSet$$RemoteValue()\n{\n DcpURL.apply(this, arguments);\n}\nexports.RemoteValue.prototype = new DcpURL('http://remote.value.dcp/prototype');\n\n/**\n * Serialize a job value, detecting the correct serialization form and MIME type; \n * JSON and KVIN selected as appropriate.\n *\n * @param {any} value - The value to serialize\n * @returns {{string: string, method:string, MIMEType: string }} - with three 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 */\nexports.serializeJobValue = function jobValues$$serializeJobValue(value)\n{\n var string;\n var methodFn;\n\n if (DcpURL.isURL(value))\n return value;\n\n switch (typeof value) {\n default:\n methodFn = KVIN;\n break;\n case 'string':\n case 'boolean':\n methodFn = JSON;\n break;\n case 'number':\n if (!isNaN(value) && value !== Infinity && value !== -Infinity && Object.is(value, -0)) // https://eslint.org/docs/latest/rules/no-compare-neg-zero\n methodFn = JSON;\n else\n methodFn = KVIN;\n break;\n case 'object':\n if (typeof value.toJSON === 'function')\n methodFn = JSON;\n else\n methodFn = KVIN;\n break;\n }\n\n string = methodFn.stringify(value);\n\n const method = methodFn === JSON ? 'json' : 'kvin';\n const MIMEType = exports.mimeTypes_bySerializer[method];\n debugging('dcp-client') && console.log('serializeJobValue:', string.length, method, MIMEType);\n return { string, method, MIMEType };\n}\n\nexports.mimeTypes_bySerializer = {\n 'kvin': 'application/x-kvin',\n 'json': 'application/json',\n};\n\n\n//# sourceURL=webpack://dcp/./src/dcp-client/job-values.js?");
|
|
4145
4218
|
|
|
4146
4219
|
/***/ }),
|
|
4147
4220
|
|
|
@@ -4152,7 +4225,7 @@ eval("/**\n * @file job-values.js\n * Utility code related t
|
|
|
4152
4225
|
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
|
4153
4226
|
|
|
4154
4227
|
"use strict";
|
|
4155
|
-
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\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 { encodeJobValueUri, 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\");\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\n/** @typedef {import('dcp/dcp-client/wallet/keystore').Keystore} Keystore */\n/** @typedef {import('dcp/dcp-client/range-object').RangeLike} RangeLike */\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 | Iterable}\n * inputData - A URI-shaped string, a [Multi]RangeObject-constructing value, or\n * an array of slice data\n * @return {RangeObject | RangeLike | DistributionRange | RemoteDataSet | Array}\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 module:dcp/dcp-events.PropagatingEventEmitter\n * @hideconstructor\n * @access public\n */\nclass Job extends PropagatingEventEmitter\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 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 // 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 const response = await this.schedulerConnection.send('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.send('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.AuthKeystore} 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('Not an instance of Keystore: ' + keystore.toString());\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 throw new Error('Not an instance of a DCP URL: ' + locationUrl);\n \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('Not an instance of a object: ' + postParams);\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\n let worker;\n this.on('accepted', () => {\n // Start a worker for this job\n worker = new Worker({\n localExec: true,\n jobAddresses: [this.address],\n allowedOrigins: this.localExecAllowedFiles,\n paymentAddress: this.paymentAccountKeystore.address,\n identity: this.identityKeystore,\n maxWorkingSandboxes: cores,\n sandboxOptions: {\n ignoreNoProgress: true,\n SandboxConstructor: (DCP_ENV.platform === 'nodejs'\n && (__webpack_require__(/*! ../worker/evaluators */ \"./src/dcp-client/worker/evaluators/index.js\").nodeEvaluatorFactory)())\n },\n });\n \n worker.on('error', (error) => {\n console.error('Worker Error:', error);\n });\n \n worker.on('warning', (warning) => {\n console.warn('Worker 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 this.localExecAllowedFiles =\n {\n any: [],\n fetchData: [],\n fetchWorkFunctions: [],\n fetchArguments: [],\n sendResults: [],\n };\n \n // Determine type of input data\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 {\n inputSetURIs.push(dataValues[i]);\n if (this.localExecAllowedFiles['fetchData'].indexOf(dataValues[i].origin) === -1)\n this.localExecAllowedFiles['fetchData'].push(dataValues[i].origin);\n }\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 if (dataPattern)\n {\n let uri = dataPattern;\n for (let i = 0; i < sliceCount; i++)\n {\n let sliceNum = i+1;\n let newURI = new URL(uri.replace('{slice}', sliceNum.toString()));\n this.localExecAllowedFiles['fetchData'].push(newURI.origin);\n }\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 this.localExecAllowedFiles['fetchData'].push(inputSetFiles[i].filename);\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 const workFunction = await fetchURI(this.workFunctionURI);\n workFunctionFile.writeSync(workFunction);\n \n const workFunctionFileURL = new URL('file://' + workFunctionFile);\n this.workFunctionURI = workFunctionFileURL.href;\n this.localExecAllowedFiles['fetchWorkFunctions'].push(workFunctionFile.filename);\n }\n else\n this.localExecAllowedFiles['fetchWorkFunctions'].push(new URL(this.workFunctionURI).origin);\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 this.localExecAllowedFiles['fetchArguments'].push(new URL(e).origin);\n encodedJobArgumentUris.push(encodeJobValueUri(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 {\n if (this.localExecAllowedFiles['fetchArguments'].indexOf(this.jobArguments[i].origin) === -1)\n this.localExecAllowedFiles['fetchArguments'].push(this.jobArguments[i].origin);\n encodedJobArgumentUris.push(encodeJobValueUri(this.jobArguments[i]));\n }\n else\n {\n if (this.jobArguments[i] instanceof RemoteDataSet) /* Member of set is RemoteDataSet */\n {\n this.jobArguments[i].forEach((e) =>\n {\n this.localExecAllowedFiles['fetchArguments'].push(new URL(e).origin);\n encodedJobArgumentUris.push(encodeJobValueUri(new URL(e)));\n });\n }\n else /* Actual Value */\n {\n const localArgFile = createTempFile(`dcp-localExec-argument-${i}-XXXXXXXXX`, 'kvin');\n localArgFile.writeSync(JSON.stringify(kvinMarshal(this.jobArguments[i])));\n this.localExecAllowedFiles['fetchArguments'].push(localArgFile.filename);\n encodedJobArgumentUris.push(encodeJobValueUri(new URL('file://' + localArgFile.filename)));\n }\n }\n } \n }\n }\n this.marshaledArguments = kvinMarshal(encodedJobArgumentUris);\n }\n \n return this.exec(...args).finally(() => {\n if (worker) {\n setTimeout(() => {\n // stop the worker\n worker.stop(true);\n }, 3000);\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 if (this.connected)\n throw new Error('Exec called twice on the same job handle.');\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'}. 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 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', { job: this });\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 const onComplete = () => resolve(this.results);\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 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 await this.eventSubscriber.close().catch(handleErr);\n await computeGroups.closeServiceConnection().catch((err) => {\n console.error('Warning: could not close compute groups service connection', err);\n });\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`, '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 let result = await fetchURI(_result);\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);\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 /* 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.jobArguments && !this.marshaledArguments)\n submitPayload.marshaledArguments = kvin.marshal(encodeJobValueList(this.jobArguments, 'jobArguments'));\n else\n submitPayload.marshaledArguments = this.marshaledArguments;\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 console.debug('Before Deploy', this.identityKeystore);\n }\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 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().catch(handleErr);\n computeGroups.closeServiceConnection().catch(handleErr);\n \n // Yes, it is possible for deployed or deployed.payload to be undefined.\n if (deployed.payload)\n {\n if (deployed.payload.code === 'ENOTFOUND')\n throw new DCPError(`Failed to submit job to scheduler. Account: ${submitPayload.paymentAccount} was not found or does not have sufficient balance (${deployed.payload.info.deployCost} DCCs needed to deploy this job)`, deployed.payload); \n throw new DCPError('Failed to submit job to scheduler', deployed.payload);\n }\n throw new DCPError('Failed to submit job to scheduler (no payload)', deployed ? 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.id,\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(...args)\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('close', () => { this.deployConnection = null; });\n }\n if (this.deployConnectionTimeout)\n clearTimeout(this.deployConnectionTimeout);\n\n debugging('deploy-connection') && console.info(`1460: sending ${args[0]} request...`);\n const deployPromise = this.deployConnection.send(...args);\n \n deployPromise.finally(() => {\n this.useDeployConnection.uses--;\n\n debugging('deploy-connection') && console.info(`1462: deployConnection done ${args[0]} 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('close');\n this.deployConnection.close();\n this.deployConnection = null;\n }\n }, (dcpConfig.job.deployCloseTimeout || 10 * 1000));\n if (!ON_BROWSER)\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\n/** \n * Encode a value list for transmission to the job-submit daemon. This could be either job arguments\n * or the 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 of URIString\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 => arguments is a list of URI strings; fetch URIs before handing to work fn\n * 2. an Array-like objects => arguments handed directly to work fn - except instances of RemoteDatum\n * All values sent to the scheduler in payload are sent in their database representation (always as some kind of URI)\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(value.href);\n else\n list.push(value);\n } \n\n const encodedList = list.map(encodeJobValueUri)\n return encodedList;\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?");
|
|
4228
|
+
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\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\");\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\n/** @typedef {import('dcp/dcp-client/wallet/keystore').Keystore} Keystore */\n/** @typedef {import('dcp/dcp-client/range-object').RangeLike} RangeLike */\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 | Iterable}\n * inputData - A URI-shaped string, a [Multi]RangeObject-constructing value, or\n * an array of slice data\n * @return {RangeObject | RangeLike | DistributionRange | RemoteDataSet | Array}\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 module:dcp/dcp-events.PropagatingEventEmitter\n * @hideconstructor\n * @access public\n */\nclass Job extends PropagatingEventEmitter\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 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 // 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.send('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.send('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.AuthKeystore} 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('Not an instance of Keystore: ' + keystore.toString());\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 throw new Error('Not an instance of a DCP URL: ' + locationUrl);\n \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('Not an instance of a object: ' + postParams);\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\n let worker;\n this.on('accepted', () => {\n // Start a worker for this job\n worker = new Worker({\n localExec: true,\n jobAddresses: [this.address],\n allowedOrigins: this.localExecAllowedFiles,\n paymentAddress: this.paymentAccountKeystore.address,\n identity: this.identityKeystore,\n maxWorkingSandboxes: cores,\n sandboxOptions: {\n ignoreNoProgress: true,\n SandboxConstructor: (DCP_ENV.platform === 'nodejs'\n && (__webpack_require__(/*! ../worker/evaluators */ \"./src/dcp-client/worker/evaluators/index.js\").nodeEvaluatorFactory)())\n },\n });\n \n worker.on('error', (error) => {\n console.error('Worker Error:', error);\n });\n \n worker.on('warning', (warning) => {\n console.warn('Worker 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 this.localExecAllowedFiles =\n {\n any: [],\n fetchData: [],\n fetchWorkFunctions: [],\n fetchArguments: [],\n sendResults: [],\n };\n \n // Determine type of input data\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 {\n inputSetURIs.push(dataValues[i]);\n if (this.localExecAllowedFiles['fetchData'].indexOf(dataValues[i].origin) === -1)\n this.localExecAllowedFiles['fetchData'].push(dataValues[i].origin);\n }\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 if (dataPattern)\n {\n let uri = dataPattern;\n for (let i = 0; i < sliceCount; i++)\n {\n let sliceNum = i+1;\n let newURI = new URL(uri.replace('{slice}', sliceNum.toString()));\n this.localExecAllowedFiles['fetchData'].push(newURI.origin);\n }\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 this.localExecAllowedFiles['fetchData'].push(inputSetFiles[i].filename);\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 const workFunction = await fetchURI(this.workFunctionURI);\n workFunctionFile.writeSync(workFunction);\n \n const workFunctionFileURL = new URL('file://' + workFunctionFile);\n this.workFunctionURI = workFunctionFileURL.href;\n this.localExecAllowedFiles['fetchWorkFunctions'].push(workFunctionFile.filename);\n }\n else\n this.localExecAllowedFiles['fetchWorkFunctions'].push(new URL(this.workFunctionURI).origin);\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 this.localExecAllowedFiles['fetchArguments'].push(new URL(e).origin);\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 {\n if (this.localExecAllowedFiles['fetchArguments'].indexOf(this.jobArguments[i].origin) === -1)\n this.localExecAllowedFiles['fetchArguments'].push(this.jobArguments[i].origin);\n encodedJobArgumentUris.push(this.jobArguments[i]);\n }\n else\n {\n if (this.jobArguments[i] instanceof RemoteDataSet) /* Member of set is RemoteDataSet */\n {\n this.jobArguments[i].forEach((e) =>\n {\n this.localExecAllowedFiles['fetchArguments'].push(new URL(e).origin);\n encodedJobArgumentUris.push(new URL(e));\n });\n }\n else /* Actual Value */\n {\n const localArgFile = createTempFile(`dcp-localExec-argument-${i}-XXXXXXXXX`, 'kvin');\n localArgFile.writeSync(JSON.stringify(kvinMarshal(this.jobArguments[i])));\n this.localExecAllowedFiles['fetchArguments'].push(localArgFile.filename);\n encodedJobArgumentUris.push(new URL('file://' + localArgFile.filename));\n }\n }\n } \n }\n }\n this.marshaledArguments = kvinMarshal(encodedJobArgumentUris);\n\n // Support for remote results with localExec.\n if (this.resultStorageDetails)\n {\n this.localExecAllowedFiles['sendResults'].push(this.resultStorageDetails.origin);\n }\n }\n \n return this.exec(...args).finally(() => {\n if (worker) {\n setTimeout(() => {\n // stop the worker\n worker.stop(true);\n }, 3000);\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 if (this.connected)\n throw new Error('Exec called twice on the same job handle.');\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'}. 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 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', { job: this });\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 // start a timer to keep the node process alive until the results come back. Keep refreshing this timer until the results promise is resovled or rejected.\n let pendingResultsTimer;\n if (DCP_ENV.platform === 'nodejs')\n {\n pendingResultsTimer = setTimeout(() => {\n pendingResultsTimer.refresh();\n }, 1000 * 60 * 60) /* hour long timer */\n }\n\n return new Promise((resolve, reject) => {\n this.startEventBandaidWatchdog();\n \n const onComplete = () => {\n if (DCP_ENV.platform === 'nodejs')\n clearTimeout(pendingResultsTimer);\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 if (DCP_ENV.platform === 'nodejs')\n clearTimeout(pendingResultsTimer); \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 await this.eventSubscriber.close().catch(handleErr);\n await computeGroups.closeServiceConnection().catch((err) => {\n console.error('Warning: could not close compute groups service connection', err);\n });\n })();\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`, '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 let result = await fetchURI(_result);\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);\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 /* 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 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().catch(handleErr);\n computeGroups.closeServiceConnection().catch(handleErr);\n \n // Yes, it is possible for deployed or deployed.payload to be undefined.\n if (deployed.payload)\n {\n if (deployed.payload.code === 'ENOTFOUND')\n throw new DCPError(`Failed to submit job to scheduler. Account: ${submitPayload.paymentAccount} was not found or does not have sufficient balance (${deployed.payload.info.deployCost} DCCs needed to deploy this job)`, deployed.payload); \n throw new DCPError('Failed to submit job to scheduler', deployed.payload);\n }\n throw new DCPError('Failed to submit job to scheduler (no payload)', deployed ? 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.id,\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(...args)\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('close', () => { this.deployConnection = null; });\n }\n if (this.deployConnectionTimeout)\n clearTimeout(this.deployConnectionTimeout);\n\n debugging('deploy-connection') && console.info(`1460: sending ${args[0]} request...`);\n const deployPromise = this.deployConnection.send(...args);\n \n deployPromise.finally(() => {\n this.useDeployConnection.uses--;\n\n debugging('deploy-connection') && console.info(`1462: deployConnection done ${args[0]} 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('close');\n this.deployConnection.close();\n this.deployConnection = null;\n }\n }, (dcpConfig.job.deployCloseTimeout || 10 * 1000));\n if (!ON_BROWSER)\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\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?");
|
|
4156
4229
|
|
|
4157
4230
|
/***/ }),
|
|
4158
4231
|
|
|
@@ -4253,7 +4326,7 @@ eval("/* provided dependency */ var process = __webpack_require__(/*! ./node_mod
|
|
|
4253
4326
|
\*************************************************/
|
|
4254
4327
|
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
4255
4328
|
|
|
4256
|
-
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 this.modal = null;\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 if (this.modal) {\n this.modal.close();\n }\n\n this.modal = window.userInterface.alert('Announcement', '' /* subtitle */, message,\n /* onClose */ () => this.modal = null);\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=
|
|
4329
|
+
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 this.modal = null;\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 if (this.modal) {\n this.modal.close();\n }\n\n this.modal = window.userInterface.alert('Announcement', '' /* subtitle */, message,\n /* onClose */ () => this.modal = null);\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=0bf54dbe88ee11e84b28e62b73cbd154d06967ea,' + 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?");
|
|
4257
4330
|
|
|
4258
4331
|
/***/ }),
|
|
4259
4332
|
|
|
@@ -4398,7 +4471,7 @@ eval("/**\n * @file node-localExec.js Node-specific support for cre
|
|
|
4398
4471
|
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
|
4399
4472
|
|
|
4400
4473
|
"use strict";
|
|
4401
|
-
eval("/**\n * @file This module implements the Worker API, used to create workers for earning DCCs.\n * @author Ryan Rossiter <ryan@kingsds.network>\n * Paul <paul@kingsds.network>\n * @date May 2020\n * June, July 2022\n * \n * @module dcp/worker\n * @access public\n */\n// @ts-check\n\n\nconst debugging = (__webpack_require__(/*! dcp/debugging */ \"./src/debugging.js\").scope)('worker');\nconst { SchedMsg } = __webpack_require__(/*! dcp/dcp-client/schedmsg */ \"./src/dcp-client/schedmsg/index.js\");\nconst { EventEmitter } = __webpack_require__(/*! dcp/common/dcp-events */ \"./src/common/dcp-events/index.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 { assert } = __webpack_require__(/*! dcp/common/dcp-assert */ \"./src/common/dcp-assert.js\");\nconst { Keystore, Address } = __webpack_require__(/*! dcp/dcp-client/wallet */ \"./src/dcp-client/wallet/index.js\");\n\n\n// To use Supervisor2 set the environment variable `USE_SUPERVISOR2`.\nconst USE_SUPERVISOR2 = Boolean((__webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\").getenv)('USE_SUPERVISOR2'));\nconst { Supervisor } = USE_SUPERVISOR2 ? __webpack_require__(/*! ./supervisor2 */ \"./src/dcp-client/worker/supervisor2/index.js\") : __webpack_require__(/*! ./supervisor */ \"./src/dcp-client/worker/supervisor.js\");\n\nconst DISABLE_WORKER_CACHE_KEY = 'disable_worker';\n\n/** @typedef {import('./sandbox').SandboxOptions} SandboxOptions */\n/** @typedef {import('../wallet/keystore').Keystore} Keystore */\n\n/**\n * @access public\n * @typedef {object} SupervisorOptions\n * @property {Address} paymentAddress - Address to deposit earned funds into\n * @property {Keystore} identity - Keystore to use as the supervisor's identity\n * @property {string[]} [jobAddresses=[]] - If set, the supervisor will only fetch work for the provided jobIDs\n * @property {boolean} [localExec=false] - If true, fetched work will not be filtered by compute groups.\n * @property {boolean} [priorityOnly=false] - Whether to only work on priority jobs, i.e. become idle if `jobAddresses` is empty.\n * @property {SandboxOptions} [sandboxOptions] - Options that will be passed to the Sandbox constructor\n * @property {number} [watchdogInterval] - Number of ms between watchdog cycles, defaults to dcpConfig tuning param\n * @property {object[]} [computeGroups] - The compute group descriptors the worker will accept jobs from (+ optionally the default compute group)\n * @property {object} [minimumWage] - The minimum payout per slice the worker will accept from a job.\n * @property {string[]} [allowedOrigins] - origins for fetching data URIs that are allowed\n * @property {boolean} [leavePublicGroup=false] - Don't fetch slices from public compute group.\n * @property {object} [schedulerConfig] - Overrides for dcpConfig.scheduler.\n * @property {number} [maxWorkingSandboxes] - Max number of concurrently working sandboxes\n * @property {{cpu: number, gpu: number}} [cores] - The number of CPU vCores and GPU devices available for compute.\n * @property {{cpu: number, gpu: number}} [targetLoad] - The proportion of the cores.cpu and cores.gpu to load.\n */\n\nfunction disableWorker() {\n localStorage.setItem(DISABLE_WORKER_CACHE_KEY, true);\n}\n\n/**\n * Fired when the worker begins fetching slices from the scheduler.\n * @access public\n * @event Worker#fetchStart\n */\nclass Worker extends EventEmitter {\n /**\n * Returns a new Worker instance.\n * @access public\n * @param {module:dcp/worker~SupervisorOptions} supervisorOptions \n */\n constructor(supervisorOptions) {\n super('Worker');\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 * @type {Supervisor}\n * @access public\n */\n this.supervisor = new Supervisor(this, supervisorOptions);\n \n debugging() && console.debug('Worker supervisorOptions:', supervisorOptions);\n\n this.supervisor.on('fetchingTask', () => this.emit('fetchStart'));\n //this.supervisor.on('fetchedTask', (fetchedSlicesCount) => this.emit('fetch', fetchedSlicesCount)); // AFAICT UNUSED -- XXXpfr\n this.supervisor.on('fetchedTask', (fetchedSlicesCount) => this.emit('fetchEnd', fetchedSlicesCount));\n this.supervisor.on('fetchTaskFailed', (error) => this.emit('fetchEnd', error));\n this.supervisor.on('fetchTaskFailed', (error) => this.emit('fetchError', error));\n\n this.supervisor.on('submittingResults', () => this.emit('submitStart'));\n this.supervisor.on('submittedResult', () => this.emit('submitEnd'));\n this.supervisor.on('submitResultsFailed', (error) => this.emit('submitEnd', error));\n this.supervisor.on('submitResultsFailed', (error) => this.emit('submitError', error));\n this.supervisor.on('submittedResult', () => this.emit('submit'));\n \n this.supervisor.on('dccCredit', (event) => this.emit('payment', event));\n this.supervisor.on('dccNoCredit', (event) => this.emit('payment', event));\n\n this.supervisor.on('sandboxReady', (sandbox) => this.emit('sandbox', sandbox));\n \n this.supervisor.on('error', (error) => this.emit('error', error));\n this.supervisor.on('warning', (warning) => this.emit('warning', warning));\n }\n\n /**\n * Disables worker instances from being started. The user will need to manually intervene to re-enable workers.\n * \n * @access public\n */\n static disableWorker() {\n disableWorker();\n }\n\n /**\n * Starts the worker.\n * \n * @access public\n */\n async start() {\n if (this.working) throw new Error('Cannot start worker: Already working.');\n\n if (localStorage.getItem(DISABLE_WORKER_CACHE_KEY)) {\n await confirmPrompt(`Worker has been disabled by the DCP Security Team; check the @DC_Protocol Twitter feed for more information before continuing.`)\n if (await confirmPrompt('Are you sure you would like to restart the worker?')) {\n localStorage.removeItem(DISABLE_WORKER_CACHE_KEY);\n console.log(\"Starting worker...\");\n } else {\n return;\n }\n }\n\n this.working = true;\n await this.supervisor.work();\n await this.schedMsg.start();\n this.emit('start');\n }\n\n /**\n * Stops the worker.\n * \n * @access public\n * @param {boolean} [immediate=false] Whether the worker should stop imediately or allow the current slices to finish.\n */\n async stop(immediate=false) {\n if (!this.working) throw new Error('Cannot stop worker: Already stopped.');\n \n this.working = false;\n debugging('shutdown') && console.debug(`151: stopping schedMsg...`);\n await this.schedMsg.stop();\n debugging('shutdown') && console.debug(`153: schedmsg stopped ok; stopping supervisor...`);\n await this.supervisor.stopWork(immediate);\n debugging('shutdown') && console.debug(`155: supervisor stopped ok`);\n this.emit('stop');\n }\n \n /**\n * Set payment address\n * @param {Address} addr - new address to be used\n */\n setPaymentAddress(addr)\n {\n assert(addr instanceof Address);\n this.supervisor.paymentAddress = addr;\n this.emit('paymentAddressChange', addr);\n }\n \n /**\n * Get payment address\n * @returns {Address} - current payment address.\n */\n getPaymentAddress()\n {\n return this.supervisor.paymentAddress;\n }\n \n /**\n * Set identity keystore.\n * Note: connections to the scheduler will only use the new identity if they are closed and recreated.\n * @param {Keystore} ks - new identity to be used\n */\n setIdentity(ks)\n {\n assert(ks instanceof Keystore);\n \n /* compatibility for supervisor 1 */\n if (this.supervisor.setDefaultIdentityKeystore)\n this.supervisor.setDefaultIdentityKeystore(ks);\n else\n this.supervisor.identity = ks;\n this.emit('identityChange', ks);\n }\n \n /**\n * Get identity keystore\n * @returns {Keystore} - the current identity keystore\n */\n getIdentity()\n {\n /* compatibiliy for supervisor 1 */\n if (this.supervisor._identityKeystore)\n return this.supervisor._identityKeystore;\n else\n return this.supervisor.identity;\n }\n \n /**\n * Set max working sandboxes\n * @param {number} max - new max working sandboxes\n */\n setMaxWorkingSandboxes(max)\n {\n this.supervisor.maxWorkingSandboxes = max;\n this.emit('maxSandboxesChange', max);\n }\n \n /**\n * Get max working sandboxes\n * @returns {number} - current max working sandboxes\n */\n getMaxWorkingSandboxes()\n {\n return this.supervisor.maxWorkingSandboxes;\n }\n \n /**\n * Check if there are any working sandboxes within the worker\n * @returns {Boolean} - true if there are working sandboxes.\n */\n hasWorkingSandboxes()\n {\n /* compatibility for supervisor 1 */\n if (this.supervisor.workingSandboxes)\n return this.supervisor.workingSandboxes.length > 0\n else\n return this.supervisor.workingSandboxCount() > 0;\n }\n}\n\nexports.Worker = Worker;\nexports.Supervisor = Supervisor;\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?");
|
|
4474
|
+
eval("/**\n * @file This module implements the Worker API, used to create workers for earning DCCs.\n * @author Ryan Rossiter <ryan@kingsds.network>\n * Paul <paul@kingsds.network>\n * @date May 2020\n * June, July 2022\n * \n * @module dcp/worker\n * @access public\n */\n// @ts-check\n\n\nconst debugging = (__webpack_require__(/*! dcp/debugging */ \"./src/debugging.js\").scope)('worker');\nconst { SchedMsg } = __webpack_require__(/*! dcp/dcp-client/schedmsg */ \"./src/dcp-client/schedmsg/index.js\");\nconst { EventEmitter } = __webpack_require__(/*! dcp/common/dcp-events */ \"./src/common/dcp-events/index.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 { assert } = __webpack_require__(/*! dcp/common/dcp-assert */ \"./src/common/dcp-assert.js\");\nconst { Keystore, Address } = __webpack_require__(/*! dcp/dcp-client/wallet */ \"./src/dcp-client/wallet/index.js\");\n\n\n// To use Supervisor2 set the environment variable `USE_SUPERVISOR2`.\nconst USE_SUPERVISOR2 = Boolean((__webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\").getenv)('USE_SUPERVISOR2'));\nconst { Supervisor } = USE_SUPERVISOR2 ? __webpack_require__(/*! ./supervisor2 */ \"./src/dcp-client/worker/supervisor2/index.js\") : __webpack_require__(/*! ./supervisor */ \"./src/dcp-client/worker/supervisor.js\");\n\nconst DISABLE_WORKER_CACHE_KEY = 'disable_worker';\n\n/** @typedef {import('./sandbox').SandboxOptions} SandboxOptions */\n/** @typedef {import('../wallet/keystore').Keystore} Keystore */\n\n/**\n * @access public\n * @typedef {object} SupervisorOptions\n * @property {Address} paymentAddress - Address to deposit earned funds into\n * @property {Keystore} identity - Keystore to use as the supervisor's identity\n * @property {string[]} [jobAddresses=[]] - If set, the supervisor will only fetch work for the provided jobIDs\n * @property {boolean} [localExec=false] - If true, fetched work will not be filtered by compute groups.\n * @property {boolean} [priorityOnly=false] - Whether to only work on priority jobs, i.e. become idle if `jobAddresses` is empty.\n * @property {SandboxOptions} [sandboxOptions] - Options that will be passed to the Sandbox constructor\n * @property {number} [watchdogInterval] - Number of ms between watchdog cycles, defaults to dcpConfig tuning param\n * @property {object[]} [computeGroups] - The compute group descriptors the worker will accept jobs from (+ optionally the default compute group)\n * @property {object} [minimumWage] - The minimum payout per slice the worker will accept from a job.\n * @property {boolean} [leavePublicGroup=false] - Don't fetch slices from public compute group.\n * @property {object} [schedulerConfig] - Overrides for dcpConfig.scheduler.\n * @property {number} [maxWorkingSandboxes] - Max number of concurrently working sandboxes\n * @property {{cpu: number, gpu: number}} [cores] - The number of CPU vCores and GPU devices available for compute.\n * @property {{cpu: number, gpu: number}} [targetLoad] - The proportion of the cores.cpu and cores.gpu to load.\n * @property {{any: string[],\n * fetchData: string[],\n * fetchWorkFunctions: string[],\n * fetchArguments: string[],\n * sendResults: string[]}} [allowedOrigins] - origins for fetching data URIs that are allowed\n */\n\nfunction disableWorker() {\n localStorage.setItem(DISABLE_WORKER_CACHE_KEY, true);\n}\n\n/**\n * Fired when the worker begins fetching slices from the scheduler.\n * @access public\n * @event Worker#fetchStart\n */\nclass Worker extends EventEmitter {\n /**\n * Returns a new Worker instance.\n * @access public\n * @param {module:dcp/worker~SupervisorOptions} supervisorOptions \n */\n constructor(supervisorOptions) {\n super('Worker');\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 * @type {Supervisor}\n * @access public\n */\n this.supervisor = new Supervisor(this, supervisorOptions);\n \n debugging() && console.debug('Worker supervisorOptions:', supervisorOptions);\n\n this.supervisor.on('fetchingTask', () => this.emit('fetchStart'));\n //this.supervisor.on('fetchedTask', (fetchedSlicesCount) => this.emit('fetch', fetchedSlicesCount)); // AFAICT UNUSED -- XXXpfr\n this.supervisor.on('fetchedTask', (fetchedSlicesCount) => this.emit('fetchEnd', fetchedSlicesCount));\n this.supervisor.on('fetchTaskFailed', (error) => this.emit('fetchEnd', error));\n this.supervisor.on('fetchTaskFailed', (error) => this.emit('fetchError', error));\n\n this.supervisor.on('submittingResults', () => this.emit('submitStart'));\n this.supervisor.on('submittedResult', () => this.emit('submitEnd'));\n this.supervisor.on('submitResultsFailed', (error) => this.emit('submitEnd', error));\n this.supervisor.on('submitResultsFailed', (error) => this.emit('submitError', error));\n this.supervisor.on('submittedResult', () => this.emit('submit'));\n \n this.supervisor.on('dccCredit', (event) => this.emit('payment', event));\n this.supervisor.on('dccNoCredit', (event) => this.emit('payment', event));\n\n this.supervisor.on('sandboxReady', (sandbox) => this.emit('sandbox', sandbox));\n \n this.supervisor.on('error', (error) => this.emit('error', error));\n this.supervisor.on('warning', (warning) => this.emit('warning', warning));\n }\n\n /**\n * Disables worker instances from being started. The user will need to manually intervene to re-enable workers.\n * \n * @access public\n */\n static disableWorker() {\n disableWorker();\n }\n\n /**\n * Starts the worker.\n * \n * @access public\n */\n async start() {\n if (this.working) throw new Error('Cannot start worker: Already working.');\n\n if (localStorage.getItem(DISABLE_WORKER_CACHE_KEY)) {\n await confirmPrompt(`Worker has been disabled by the DCP Security Team; check the @DC_Protocol Twitter feed for more information before continuing.`)\n if (await confirmPrompt('Are you sure you would like to restart the worker?')) {\n localStorage.removeItem(DISABLE_WORKER_CACHE_KEY);\n console.log(\"Starting worker...\");\n } else {\n return;\n }\n }\n\n this.working = true;\n await this.supervisor.work();\n await this.schedMsg.start();\n this.emit('start');\n }\n\n /**\n * Stops the worker.\n * \n * @access public\n * @param {boolean} [immediate=false] Whether the worker should stop imediately or allow the current slices to finish.\n */\n async stop(immediate=false) {\n if (!this.working) throw new Error('Cannot stop worker: Already stopped.');\n \n this.working = false;\n debugging('shutdown') && console.debug(`151: stopping schedMsg...`);\n await this.schedMsg.stop();\n debugging('shutdown') && console.debug(`153: schedmsg stopped ok; stopping supervisor...`);\n await this.supervisor.stopWork(immediate);\n debugging('shutdown') && console.debug(`155: supervisor stopped ok`);\n this.emit('stop');\n }\n \n /**\n * Set payment address\n * @param {Address} addr - new address to be used\n */\n setPaymentAddress(addr)\n {\n assert(addr instanceof Address);\n this.supervisor.paymentAddress = addr;\n this.emit('paymentAddressChange', addr);\n }\n \n /**\n * Get payment address\n * @returns {Address} - current payment address.\n */\n getPaymentAddress()\n {\n return this.supervisor.paymentAddress;\n }\n \n /**\n * Set identity keystore.\n * Note: connections to the scheduler will only use the new identity if they are closed and recreated.\n * @param {Keystore} ks - new identity to be used\n */\n setIdentity(ks)\n {\n assert(ks instanceof Keystore);\n \n /* compatibility for supervisor 1 */\n if (this.supervisor.setDefaultIdentityKeystore)\n this.supervisor.setDefaultIdentityKeystore(ks);\n else\n this.supervisor.identity = ks;\n this.emit('identityChange', ks);\n }\n \n /**\n * Get identity keystore\n * @returns {Keystore} - the current identity keystore\n */\n getIdentity()\n {\n /* compatibiliy for supervisor 1 */\n if (this.supervisor._identityKeystore)\n return this.supervisor._identityKeystore;\n else\n return this.supervisor.identity;\n }\n \n /**\n * Set max working sandboxes\n * @param {number} max - new max working sandboxes\n */\n setMaxWorkingSandboxes(max)\n {\n this.supervisor.maxWorkingSandboxes = max;\n this.emit('maxSandboxesChange', max);\n }\n \n /**\n * Get max working sandboxes\n * @returns {number} - current max working sandboxes\n */\n getMaxWorkingSandboxes()\n {\n return this.supervisor.maxWorkingSandboxes;\n }\n \n /**\n * Check if there are any working sandboxes within the worker\n * @returns {Boolean} - true if there are working sandboxes.\n */\n hasWorkingSandboxes()\n {\n /* compatibility for supervisor 1 */\n if (this.supervisor.workingSandboxes)\n return this.supervisor.workingSandboxes.length > 0\n else\n return this.supervisor.workingSandboxCount() > 0;\n }\n}\n\nexports.Worker = Worker;\nexports.Supervisor = Supervisor;\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?");
|
|
4402
4475
|
|
|
4403
4476
|
/***/ }),
|
|
4404
4477
|
|
|
@@ -4441,7 +4514,7 @@ eval("/**\n * @file worker/supervisor-cache.js\n *\n * A cache for the superviso
|
|
|
4441
4514
|
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
|
4442
4515
|
|
|
4443
4516
|
"use strict";
|
|
4444
|
-
eval("/* provided dependency */ var process = __webpack_require__(/*! ./node_modules/process/browser.js */ \"./node_modules/process/browser.js\");\n/**\n * @file worker/supervisor.js\n *\n * The component that controls each of the sandboxes\n * and distributes work to them. Also communicates with the\n * scheduler to fetch said work.\n *\n * The supervisor readies sandboxes before/while fetching slices.\n * This means sometimes there are extra instantiated WebWorkers\n * that are idle (in this.readiedSandboxes). Readied sandboxes can\n * be used for any slice. After a readied sandbox is given a slice\n * it becomes assigned to slice's job and can only do work\n * for that job.\n *\n * After a sandbox completes its work, the sandbox becomes cached\n * and can be reused if another slice with a matching job is fetched.\n *\n * @author Matthew Palma, mpalma@kingsds.network\n * Ryan Rossiter, ryan@kingsds.network\n * @date May 2019\n */\n\n/* global dcpConfig */\n// @ts-check\n\n\nconst constants = __webpack_require__(/*! dcp/common/scheduler-constants */ \"./src/common/scheduler-constants.js\");\nconst hash = __webpack_require__(/*! dcp/common/hash */ \"./src/common/hash.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 DCP_ENV = __webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\");\n\nconst debugging = (__webpack_require__(/*! dcp/debugging */ \"./src/debugging.js\").scope)('worker');\nconst { assert } = __webpack_require__(/*! dcp/common/dcp-assert */ \"./src/common/dcp-assert.js\");\nconst { EventEmitter } = __webpack_require__(/*! dcp/common/dcp-events */ \"./src/common/dcp-events/index.js\");\nconst { Sandbox, SandboxError } = __webpack_require__(/*! ./sandbox */ \"./src/dcp-client/worker/sandbox.js\");\nconst { Slice, SLICE_STATUS_UNASSIGNED, SLICE_STATUS_FAILED } = __webpack_require__(/*! ./slice */ \"./src/dcp-client/worker/slice.js\");\nconst { SupervisorCache } = __webpack_require__(/*! ./supervisor-cache */ \"./src/dcp-client/worker/supervisor-cache.js\");\nconst { DcpURL } = __webpack_require__(/*! dcp/common/dcp-url */ \"./src/common/dcp-url.js\");\nconst { requireNative } = __webpack_require__(/*! dcp/dcp-client/webpack-native-bridge */ \"./src/dcp-client/webpack-native-bridge.js\");\nconst { localStorage } = __webpack_require__(/*! dcp/common/dcp-localstorage */ \"./src/common/dcp-localstorage.js\");\nconst { booley, encodeDataURI, makeValueURI, leafMerge, a$sleepMs, justFetch, compressJobMap, toJobMap,\n compressSandboxes, compressSlices, truncateAddress, dumpSandboxesIfNotUnique, dumpSlicesIfNotUnique, \n generateOpaqueId } = __webpack_require__(/*! dcp/utils */ \"./src/utils/index.js\");\nconst { DCPError } = __webpack_require__(/*! dcp/common/dcp-error */ \"./src/common/dcp-error.js\");\nconst { sliceStatus } = __webpack_require__(/*! dcp/common/scheduler-constants */ \"./src/common/scheduler-constants.js\");\nconst { calculateJoinHash } = __webpack_require__(/*! dcp/dcp-client/compute-groups */ \"./src/dcp-client/compute-groups/index.js\");\nconst RingBuffer = __webpack_require__(/*! dcp/utils/ringBuffer */ \"./src/utils/ringBuffer.js\");\nconst supervisorTuning = dcpConfig.future('worker.tuning');\nconst tuning = {\n watchdogInterval: 7, /**< seconds - time between fetches when ENOTASK(? /wg nov 2019) */\n minSandboxStartDelay: 0.1, /**< seconds - minimum time between WebWorker starts */\n maxSandboxStartDelay: 0.7, /**< seconds - maximum delay time between WebWorker starts */\n ...supervisorTuning\n};\n\n/** Make timers 10x slower when running in niim */\nlet timeDilation = 1;\nif (DCP_ENV.platform === 'nodejs') {\n /** Make timers 10x slower when running in niim */\n timeDilation = (requireNative('module')._cache.niim instanceof requireNative('module').Module) ? 10 : 1;\n}\n\ndcpConfig.future('worker.sandbox', { progressReportInterval: (5 * 60 * 1000) });\nconst sandboxTuning = dcpConfig.worker.sandbox;\n\n/**\n * @typedef {*} address\n * @typedef {*} opaqueId\n */\n\n/**\n * @typedef {object} SandboxSlice\n * @property {Sandbox} sandbox\n * @property {Slice} slice\n */\n\n/**\n * @typedef {object} Signature\n * @property {Uint8Array} r\n * @property {Uint8Array} s\n * @property {Uint8Array} v\n */\n\n/**\n * @typedef {object} SignedAuthorizationMessageObject\n * @property {object} auth\n * @property {Signature} signature\n * @property {module:dcp/wallet.Address} owner\n */\n\n/** @typedef {import('.').Worker} Worker */\n/** @typedef {import('.').SupervisorOptions} SupervisorOptions */\n\nclass Supervisor extends EventEmitter {\n /**\n * @constructor\n * @param {Worker} worker\n * @param {SupervisorOptions} options\n */\n constructor (worker, options={}) {\n super('Supervisor');\n\n /** @type {Worker} */\n this.worker = worker;\n\n /** @type {Sandbox[]} */\n this.sandboxes = [];\n\n /** @type {Sandbox[]} */\n this.readiedSandboxes = [];\n\n /** @type {Sandbox[]} */\n this.assignedSandboxes = [];\n\n /** @type {Slice[]} */\n this.slices = [];\n\n /** @type {Slice[]} */\n this.queuedSlices = [];\n\n /** @type {Slice[]} */\n this.lostSlices = [];\n\n /** @type {boolean} */\n this.matching = false;\n\n /** @type {boolean} */\n this.isFetchingNewWork = false;\n\n /** @type {number} */\n this.numberOfCoresReserved = 0;\n\n /** @type {number} */\n this.addressTruncationLength = 20; // Set to -1 for no truncation.\n\n /** @type {Object[]} */\n this.rejectedJobs = [];\n this.rejectedJobReasons = [];\n\n if (!options) {\n console.error('Supervisor Options', options, new Error().stack);\n options = {};\n }\n\n /** @type {object} */\n this.options = {\n jobAddresses: options.jobAddresses || [/* all jobs unless priorityOnly */],\n ...options,\n };\n\n const { paymentAddress, identity } = options;\n if (paymentAddress) {\n if (paymentAddress instanceof wallet.Keystore) {\n this.paymentAddress = paymentAddress.address;\n } else {\n this.paymentAddress = new wallet.Address(paymentAddress);\n }\n } else {\n this.paymentAddress = null;\n }\n\n this._identityKeystore = identity;\n\n this.extraAllowOrigins = {\n any: [],\n fetchData: [],\n fetchWorkFunctions: [],\n fetchArguments: [],\n sendResults: [],\n };\n \n if (typeof options.allowedOrigins !== 'undefined')\n {\n console.warn('Warning: using deprecated interface options.allowedOrigins; callstack=', new Error().stack.split('\\n').slice(1));\n if (!Array.isArray(options.allowedOrigins))\n {\n for (let kind in this.extraAllowOrigins)\n {\n if (options.allowedOrigins[kind])\n this.extraAllowOrigins[kind].push(...options.allowedOrigins[kind]); \n }\n }\n else\n this.extraAllowOrigins['any'].push(...options.allowedOrigins)\n \n delete options.allowedOrigins;\n }\n /* Assume that an array of dcpConfig.worker.allowOrigins means they can be used for anything */\n if (Array.isArray(dcpConfig.worker.allowOrigins))\n dcpConfig.worker.allowOrigins = { any: dcpConfig.worker.allowOrigins };\n \n /**\n * Maximum sandboxes allowed to work at a given time.\n * @type {number}\n */\n this.maxWorkingSandboxes = options.maxWorkingSandboxes || 1;\n\n /** @type {number} */\n this.defaultMaxGPUs = 1;\n // this.GPUsAssigned = 0;\n \n // Object.defineProperty(this, 'GPUsAssigned', {\n // get: () => this.allocatedSandboxes.filter(sb => !!sb.requiresGPU).length,\n // enumerable: true,\n // configurable: false,\n // });\n\n /**\n * TODO: Remove this when the supervisor sends all of the sandbox\n * capabilities to the scheduler when fetching work.\n * @type {object}\n */\n this.capabilities = null;\n\n /** @type {number} */\n this.lastProgressReport = 0;\n\n /** \n * An N-slot ring buffer of job addresses. Stores all jobs that have had no more than 1 slice run in the ring buffer.\n * Required for the implementation of discrete jobs \n * @type {RingBuffer} \n */\n this.ringBufferofJobs = new RingBuffer(200); // N = 200 should be more than enough.\n \n // @hack - dcp-env.isBrowserPlatform is not set unless the platform is _explicitly_ set,\n // using the default detected platform doesn't set it.\n // Fixing that causes an error in the wallet module's startup on web platform, which I\n // probably can't fix in a reasonable time this morning.\n // ~ER2020-02-20\n\n if (!options.maxWorkingSandboxes\n && DCP_ENV.browserPlatformList.includes(DCP_ENV.platform)\n && navigator.hardwareConcurrency > 1) {\n this.maxWorkingSandboxes = navigator.hardwareConcurrency - 1;\n if (typeof navigator.userAgent === 'string') {\n if (/(Android).*(Chrome|Chromium)/.exec(navigator.userAgent)) {\n this.maxWorkingSandboxes = 1;\n console.log('Doing work with Chromimum browsers on Android is currently limited to one sandbox');\n }\n }\n }\n\n /** @type {SupervisorCache} */\n this.cache = new SupervisorCache(this);\n /** @type {object} */\n this._connections = {}; /* active DCPv4 connections */\n // Call the watchdog every 7 seconds.\n this.watchdogInterval = setInterval(() => this.watchdog(), tuning.watchdogInterval * 1000);\n if (DCP_ENV.platform === 'nodejs' && this.options.localExec) /* interval helps keep normal worker alive forever, which we don't want in localexec */\n this.watchdogInterval.unref();\n\n const ceci = this;\n\n // Initialize to null so these properties are recognized for the Supervisor class\n this.taskDistributorConnection = null;\n this.eventRouterConnection = null;\n this.resultSubmitterConnection = null;\n this.packageManagerConnection = null;\n this.openTaskDistributorConn = function openTaskDistributorConn()\n {\n let config = dcpConfig.scheduler.services.taskDistributor;\n ceci.taskDistributorConnection = new protocolV4.Connection(config, ceci.identityKeystore, connectionOptions(config.location, 'taskDistributor'));\n ceci.taskDistributorConnection.on('close', ceci.openTaskDistributorConn);\n }\n\n this.openEventRouterConn = function openEventRouterConn()\n {\n let config = dcpConfig.scheduler.services.eventRouter;\n ceci.eventRouterConnection = new protocolV4.Connection(config, ceci.identityKeystore, connectionOptions(config.location, 'eventRouter'));\n ceci.eventRouterConnection.on('close', ceci.openEventRouterConn);\n if (ceci.eventRouterMessageQueue.length)\n ceci.resendRejectedMessages(ceci.eventRouterConnection, ceci.eventRouterMessageQueue);\n }\n this.eventRouterMessageQueue = [];\n \n this.openResultSubmitterConn = function openResultSubmitterConn()\n {\n let config = dcpConfig.scheduler.services.resultSubmitter;\n ceci.resultSubmitterConnection = new protocolV4.Connection(config, ceci.identityKeystore, connectionOptions(config.location, 'resultSubmitter'));\n ceci.resultSubmitterConnection.on('close', ceci.openResultSubmitterConn);\n if (ceci.resultSubmitterMessageQueue.length)\n ceci.resendRejectedMessages(ceci.resultSubmitterConnection, ceci.resultSubmitterMessageQueue);\n }\n this.resultSubmitterMessageQueue = [];\n\n this.openPackageManagerConn = function openPackageManagerConn()\n {\n let config = dcpConfig.packageManager;\n ceci.packageManagerConnection = new protocolV4.Connection(config, ceci.identityKeystore, connectionOptions(config.location, 'packageManager'));\n ceci.packageManagerConnection.on('close', ceci.openPackageManagerConn);\n if (ceci.packageManagerMessageQueue.length)\n ceci.resendRejectedMessages(ceci.packageManagerConnection, ceci.packageManagerMessageQueue);\n }\n this.packageManagerMessageQueue = [];\n }\n\n /**\n * Return worker opaqueId.\n * @type {opaqueId}\n */\n get workerOpaqueId() {\n if (!this._workerOpaqueId)\n this._workerOpaqueId = localStorage.getItem('workerOpaqueId');\n\n if (!this._workerOpaqueId || this._workerOpaqueId.length !== constants.workerIdLength) {\n this._workerOpaqueId = generateOpaqueId();\n localStorage.setItem('workerOpaqueId', this._workerOpaqueId);\n }\n\n return this._workerOpaqueId;\n }\n\n /**\n * This getter is the absolute source-of-truth for what the\n * identity keystore is for this instance of the Supervisor.\n */\n get identityKeystore() {\n assert(this.defaultIdentityKeystore);\n\n return this._identityKeystore || this.defaultIdentityKeystore;\n }\n \n \n /** \n * Factory function which generates a list of origins which are safe to communicate \n * with for this purpose. Currently-valid purposes (more will be added):\n * - any\n * - fetchData\n * - fetchWork\n * - fetchWorkArguments\n * - sendResults\n */\n makeSafeOriginList(purpose)\n {\n var list = [];\n\n assert(Array.isArray(this.extraAllowOrigins[purpose]));\n \n if (this.extraAllowOrigins[purpose])\n list = list.concat(this.extraAllowOrigins[purpose]);\n if (dcpConfig.worker.allowOrigins[purpose])\n list = list.concat(dcpConfig.worker.allowOrigins[purpose])\n\n // In localExec, do not allow work function or arguments to come from the 'any' origins\n if (purpose !== 'any' && (!this.options.localExec || (this.options.localExec && purpose === 'sendResults')))\n {\n if (this.extraAllowOrigins.any)\n list = list.concat(this.extraAllowOrigins.any);\n if (dcpConfig.worker.allowOrigins.any)\n list = list.concat(dcpConfig.worker.allowOrigins.any);\n }\n return list;\n }\n\n /**\n * Open all connections. Used when supervisor is instantiated or stopped/started\n * to initially open connections.\n */\n instantiateAllConnections() {\n if (!this.taskDistributorConnection)\n this.openTaskDistributorConn();\n \n if (!this.eventRouterConnection)\n this.openEventRouterConn();\n \n if (!this.resultSubmitterConnection)\n this.openResultSubmitterConn();\n\n if (!this.packageManagerConnection)\n this.openPackageManagerConn();\n }\n \n /**\n * Asynchronously send a result to the result submitter that was previously rejected.\n * Different from resendRejectedMessages below in the sense that the function only resolves\n * once we've delivered the result, or gone past our max number of attempts.\n * @param {object} result \n * @returns the response payload from the result operation\n */\n async resendResult(result) {\n var protocolError = false;\n if (!result.sendRetries)\n result.sendRetries = 1;\n else\n result.sendRetries++;\n \n if (result.sendRetries > dcpConfig.worker.maxResultSubmissionRetries)\n throw new DCPError(`Could not submit result after ${dcpConfig.worker.maxResultSubmissionRetries} attempts. Aborting.`) \n \n debugging() && console.debug(`supervisor - failed to submit result ${result.sendRetries} time(s), trying again `)\n let res = await this.resultSubmitterConnection.send('result', result).catch(async (e) => {\n debugging('supervisor') && console.error(`Failed to submit result to scheduler for slice ${result.slice} of job ${result.job}:\\n ${e} \\nWill try again on new connection.`);\n this.resultSubmitterConnection.close();\n await a$sleepMs(10); /* let connection recycle */\n protocolError = true;\n });\n if ((!res.success && res.payload && res.payload.code === 'DCPS-01002') || protocolError)\n return this.resendResult(result)\n else\n return res;\n }\n \n /**\n * Try sending messages that were rejected on an old instance of the given connection.\n * These are messages that a) were rejected due to a protocol error and b) don't care when exactly\n * they're sent in the grand scheme of things.\n */\n resendRejectedMessages(connection, messageQueue) {\n if (connection.resendingMessages) /* if the passed connection is already in the loop, exit */\n return;\n \n var message = messageQueue.shift();\n\n do {\n \n connection.resendingMessages = true;\n var quitLoop = false;\n \n connection.send(message.operation, message.data)\n .catch((e) =>\n {\n /* Protocol Error; Close connection (this will trigger the opening of a new connection that will try sending again) */\n debugging('supervisor') && console.error(`Failed to send message ${message.operation} to scheduler: ${e}. Will try again on a new \n connection.`);\n messageQueue.unshift(message);\n connection.close();\n quitLoop = true;\n });\n \n message = messageQueue.shift();\n \n } while (message && !quitLoop)\n\n connection.resendingMessages = false;\n }\n\n /** Set the default identity keystore -- needs to happen before anything that talks\n * to the scheduler for work gets called. This is a wart and should be removed by\n * refactoring.\n *\n * The default identity keystore will be used if the Supervisor was not provided\n * with an alternate. This keystore will be located via the Wallet API, and \n * if not found, a randomized default identity will be generated. \n *\n * @param {object} ks An instance of wallet::Keystore -- if undefined, we pick the best default we can.\n * @returns {Promise<void>}\n */\n async setDefaultIdentityKeystore(ks) {\n try {\n if (ks) {\n this.defaultIdentityKeystore = ks;\n return;\n }\n\n if (this.defaultIdentityKeystore)\n return;\n\n try {\n this.defaultIdentityKeystore = await wallet.getId();\n } catch(e) {\n debugging('supervisor') && console.debug('Error generating default identity, try to do it another way.');\n this.defaultIdentityKeystore = await new wallet.IdKeystore(null, '');\n }\n } finally {\n if (this.defaultIdentityKeystore)\n debugging('supervisor') && console.debug('Set default identity =', this.defaultIdentityKeystore.address);\n else\n debugging('supervisor') && console.debug('Failed to set default identity, worker cannot work.');\n }\n }\n\n //\n // What follows is a bunch of utility properties and functions for creating filtered views\n // of the slices and sandboxes array.\n //\n /** XXXpfr @todo Write sort w/o using promises so we can get rid of async on all the compress functions. */\n\n /**\n * @deprecated -- Please do not use this.workingSandboxes; use this.allocatedSandboxes instead.\n * Sandboxes that are in WORKING state.\n *\n * Warning: Do not rely on this information being 100% accurate -- it may change in the next instant.\n * @type {Sandbox[]}\n */\n get workingSandboxes() {\n return this.sandboxes.filter(sandbox => sandbox.isWorking);\n }\n\n /**\n * Use instead of this.workingSandboxes.\n *\n * When a sandbox is paired with a slice, execution is pending and sandbox.allocated=true and\n * sandbox.slice=slice and sandbox.jobAddress=slice.jobAddress. This is what 'allocated' means.\n * Immediately upon the exit of sandbox.work, sandbox.allocated=false is set and if an exception\n * wasn't thrown the sandbox is placed in this.assignedSandboxes.\n * Thus from the pov of supervisor, this.allocatedSandboxes is deterministic and this.workingSandboxes is not.\n * Please try to not use this.workingSandboxes. It is deprecated.\n *\n * Warning: Do not rely on this information being 100% accurate -- it may change in the next instant.\n * @type {Sandbox[]}\n */\n get allocatedSandboxes() {\n return this.sandboxes.filter(sandbox => sandbox.allocated);\n }\n\n /**\n * Slices that are allocated.\n * Warning: Do not rely on this information being 100% accurate -- it may change in the next instant.\n * @type {Slice[]}\n */\n get allocatedSlices() {\n return this.slices.filter(slice => slice.allocated);\n }\n\n /**\n * This property is used as the target number of sandboxes to be associated with slices and start working.\n *\n * It is used in this.watchdog as to prevent a call to this.work when unallocatedSpace <= 0.\n * It is also used in this.distributeQueuedSlices where it is passed as an argument to this.matchSlicesWithSandboxes to indicate how many sandboxes\n * to associate with slices and start working.\n *\n * Warning: Do not rely on this information being 100% accurate -- it may change in the next instant.\n * @type {number}\n */\n get unallocatedSpace() {\n return this.maxWorkingSandboxes - this.allocatedSandboxes.length - this.numberOfCoresReserved;\n }\n \n /**\n * Call acquire(numberOfCoresToReserve) to reserve numberOfCoresToReserve unallocated sandboxes as measured by unallocatedSpace.\n * Call release() to undo the previous acquire.\n * This pseudo-mutex technique helps prevent races in scheduling slices in Supervisor.\n * @param {number} numberOfCoresToReserve\n */\n acquire(numberOfCoresToReserve) { \n this.numberOfCoresReserved = numberOfCoresToReserve; \n }\n release() { \n this.numberOfCoresReserved = 0; \n }\n\n /**\n * Remove from this.slices.\n * @param {Slice} slice\n */\n removeSlice(slice) {\n this.removeElement(this.slices, slice);\n if (Supervisor.debugBuild) {\n if (this.queuedSlices.indexOf(slice) !== -1)\n throw new Error(`removeSlice: slice ${slice.identifier} is in queuedSlices; inconsistent state.`);\n if (this.lostSlices.length > 0) {\n console.warn(`removeSlice: slice ${slice.identifier}, found lostSlices ${this.lostSlices.map(s => s.identifier)}`);\n if (this.lostSlices.indexOf(slice) !== -1)\n throw new Error(`removeSlice: slice ${slice.identifier} is in lostSlices; inconsistent state.`);\n }\n }\n }\n\n /**\n * Remove from this.slices.\n * @param {Slice[]} slices\n */\n removeSlices(slices) {\n this.slices = this.slices.filter(slice => slices.indexOf(slice) === -1);\n }\n\n /**\n * Remove from this.queuedSlices.\n * @param {Slice[]} slices\n */\n removeQueuedSlices(slices) {\n this.queuedSlices = this.queuedSlices.filter(slice => slices.indexOf(slice) === -1);\n }\n\n /**\n * Remove from this.sandboxes, this.assignedSandboxes and this.readiedSandboxes.\n * @param {Sandbox} sandbox\n */\n removeSandbox(sandbox) {\n debugging('scheduler') && console.log(`removeSandbox ${sandbox.identifier}`);\n this.removeElement(this.sandboxes, sandbox);\n this.removeElement(this.assignedSandboxes, sandbox);\n\n // XXXpfr: April 13, 2022\n // I'm trying to understand and control when sandboxes get removed.\n // A sandbox in this.readiedSandboxes should never have returnSandbox/removeSandbox called on it except in stopWork.\n // Because of races and random worker crashes, it is hard to get this right, but I want to try.\n // If I don't fix this is the next 30 days or I forget, please delete this exception.\n if (false)\n {}\n\n this.removeElement(this.readiedSandboxes, sandbox);\n }\n\n /**\n * Remove from this.sandboxes and this.assignedSandboxes .\n * @param {Sandbox[]} sandboxes\n */\n async removeSandboxes(sandboxes) {\n debugging('scheduler') && console.log(`removeSandboxes: Remove ${sandboxes.length} sandboxes ${this.dumpSandboxes(sandboxes)}`);\n this.sandboxes = this.sandboxes.filter(sandbox => sandboxes.indexOf(sandbox) === -1);\n this.assignedSandboxes = this.assignedSandboxes.filter(sandbox => sandboxes.indexOf(sandbox) === -1);\n\n if (Supervisor.debugBuild) {\n const readied = this.readiedSandboxes.filter(sandbox => sandboxes.indexOf(sandbox) !== -1);\n if (readied.length > 0)\n throw new Error(`removeSandboxes: sandboxes ${readied.map(s => s.identifier)} are in readiedSandboxes; inconsistent state.`);\n }\n }\n\n /**\n * Remove element from theArray.\n * @param {Array<*>} theArray\n * @param {object|number} element\n * @param {boolean} [assertExists = true]\n */\n removeElement(theArray, element, assertExists = false) {\n let index = theArray.indexOf(element);\n assert(index !== -1 || !assertExists);\n if (index !== -1) theArray.splice(index, 1);\n }\n\n /**\n * Log sliceArray.\n * @param {Slice[]} sliceArray\n * @param {string} [header]\n * @returns {string}\n */\n dumpSlices(sliceArray, header) {\n if (header) console.log(`\\n${header}`);\n return compressSlices(sliceArray, this.addressTruncationLength);\n }\n\n /**\n * Log sandboxArray.\n * @param {Sandbox[]} sandboxArray\n * @param {string} [header]\n * @returns {string}\n */\n dumpSandboxes(sandboxArray, header) {\n if (header) console.log(`\\n${header}`);\n return compressSandboxes(sandboxArray, this.addressTruncationLength);\n }\n\n /**\n * If the elements of sandboxSliceArray are not unique, log the duplicates and dump the array.\n * @param {SandboxSlice[]} sandboxSliceArray\n * @param {string} header\n */\n dumpSandboxSlicesIfNotUnique(sandboxSliceArray, header) {\n if (!this.isUniqueSandboxSlices(sandboxSliceArray, header))\n console.log(this.dumpSandboxSlices(sandboxSliceArray));\n }\n\n /**\n * Log { sandbox, slice }.\n * @param {Sandbox} sandbox\n * @param {Slice} slice\n * @returns {string}\n */\n dumpSandboxAndSlice(sandbox, slice) {\n return `${sandbox.id}~${slice.sliceNumber}.${this.dumpJobAddress(slice.jobAddress)}`;\n }\n\n /**\n * Log { sandbox, slice } with state/status.\n * @param {Sandbox} sandbox\n * @param {Slice} slice\n * @returns {string}\n */\n dumpStatefulSandboxAndSlice(sandbox, slice) {\n return `${sandbox.id}.${sandbox.state}~${slice.sliceNumber}.${this.dumpJobAddress(slice.jobAddress)}.${slice.status}`;\n }\n\n /**\n * Truncates jobAddress.toString() to this.addressTruncationLength digits.\n * @param {address} jobAddress\n * @returns {string}\n */\n dumpJobAddress(jobAddress) {\n return truncateAddress(jobAddress, this.addressTruncationLength /* digits*/);\n }\n\n /**\n * Dump sandboxSliceArray.\n * @param {SandboxSlice[]} sandboxSliceArray - input array of { sandbox, slice }\n * @param {string} [header] - optional header\n * @param {boolean} [stateFul] - when true, also includes slice.status and sandbox.state.\n * @returns {string}\n */\n dumpSandboxSlices(sandboxSliceArray, header, stateFul=false) {\n if (header) console.log(`\\n${header}`);\n const jobMap = {};\n sandboxSliceArray.forEach(ss => {\n const sss = stateFul ? `${ss.sandbox.id}.${ss.sandbox.state}~${ss.slice.sliceNumber}.${ss.slice.status}` : `${ss.sandbox.id}~${ss.slice.sliceNumber}`;\n if (!jobMap[ss.slice.jobAddress]) jobMap[ss.slice.jobAddress] = sss;\n else jobMap[ss.slice.jobAddress] += `,${sss}`;\n });\n let output = '';\n for (const [jobAddress, sss] of Object.entries(jobMap))\n output += `${this.dumpJobAddress(jobAddress)}:[${sss}]:`;\n return output;\n }\n\n /**\n * Check sandboxSliceArray for duplicates.\n * @param {SandboxSlice[]} sandboxSliceArray\n * @param {string} [header]\n * @param {function} [log]\n * @returns {boolean}\n */\n isUniqueSandboxSlices(sandboxSliceArray, header, log) {\n const result = [], slices = [], sandboxes = [];\n let once = true;\n sandboxSliceArray.forEach(x => {\n const sliceIndex = slices.indexOf(x.slice);\n const sandboxIndex = sandboxes.indexOf(x.sandbox);\n\n if (sandboxIndex >= 0) {\n if (once && header) console.log(`\\n${header}`); once = false;\n log ? log(x.sandbox) : console.log(`\\tWarning: Found duplicate sandbox ${x.sandbox.identifier}.`);\n } else sandboxes.push(x.sandbox);\n\n if (sliceIndex >= 0) {\n if (once && header) console.log(`\\n${header}`); once = false;\n log ? log(x.slice) : console.log(`\\tWarning: Found duplicate slice ${x.slice.identifier}.`);\n } else {\n slices.push(x.slice);\n if (sandboxIndex < 0) result.push(x);\n }\n });\n return sandboxSliceArray.length === result.length;\n }\n\n /**\n * Attempts to create and start a given number of sandboxes.\n * The sandboxes that are created can then be assigned for a\n * specific job at a later time. All created sandboxes\n * get put into the @this.readiedSandboxes array when allocateLocalSandboxes is false.\n *\n * @param {number} numSandboxes - the number of sandboxes to create\n * @param {boolean} [allocateLocalSandboxes=false] - when true, do not place in this.readiedSandboxes\n * @returns {Promise<Sandbox[]>} - resolves with array of created sandboxes, rejects otherwise\n * @throws when given a numSandboxes is not a number or if numSandboxes is Infinity\n */\n async readySandboxes (numSandboxes, allocateLocalSandboxes = false) {\n debugging('supervisor') && console.debug(`readySandboxes: Readying ${numSandboxes} sandboxes, total sandboxes ${this.sandboxes.length}, matching ${this.matching}, fetching ${this.isFetchingNewWork}`);\n \n if (typeof numSandboxes !== 'number' || Number.isNaN(numSandboxes) || numSandboxes === Infinity) {\n throw new Error(`${numSandboxes} is not a number of sandboxes that can be readied.`);\n }\n if (numSandboxes <= 0) {\n return [];\n }\n\n const sandboxStartPromises = [];\n const sandboxes = [];\n const errors = [];\n for (let i = 0; i < numSandboxes; i++) {\n const sandbox = new Sandbox(this, {\n ...this.options.sandboxOptions,\n });\n sandbox.addListener('ready', () => this.emit('sandboxReady', sandbox));\n sandbox.addListener('start', () => {\n this.emit('sandboxStart', sandbox);\n\n // When sliceNumber == 0, result-submitter status skips the slice,\n // so don't send it in the first place.\n // The 'start' event is fired when a worker starts up, hence there's no way\n // to determine whether sandbox has a valid slice without checking.\n if (sandbox.slice) {\n const jobAddress = sandbox.jobAddress;\n const sliceNumber = sandbox.slice.sliceNumber;\n // !authorizationMessage <==> sliceNumber === 0.\n const authorizationMessage = sandbox.slice.getAuthorizationMessage();\n\n if (authorizationMessage) {\n let statusPayload = {\n worker: this.workerOpaqueId,\n slices: [{\n job: jobAddress,\n sliceNumber: sliceNumber,\n status: 'begin',\n authorizationMessage,\n }],\n }\n \n try /* resultSubmitterConnection can be null if worker is stopped */\n {\n this.resultSubmitterConnection.send('status', statusPayload).catch((error) => {\n debugging('supervisor') && console.error(`Error sending 'status' for slice ${sliceNumber} of job ${jobAddress}:\\n ${error}\\nWill try again on a new connection`);\n this.resultSubmitterMessageQueue.push({ operation: 'status', data: statusPayload });\n this.resultSubmitterConnection.close();\n });\n } catch (error)\n {\n debugging('supervisor') && console.error(`Failed to send 'status' for slice ${sliceNumber} of job ${jobAddress}:, no connection to result submitter:`, error);\n }\n }\n }\n });\n sandbox.addListener('workEmit', ({ eventName, payload }) => {\n // Need to check if the sandbox hasn't been assigned a slice yet.\n if (!sandbox.slice) {\n if (Supervisor.debugBuild) {\n console.error(\n `Sandbox not assigned a slice before sending workEmit message to scheduler. 'workEmit' event originates from \"${eventName}\" event`, \n payload,\n );\n }\n }\n else\n {\n const jobAddress = sandbox.slice.jobAddress;\n const sliceNumber = sandbox.slice.sliceNumber;\n // sliceNumber can be zero if it came from a problem with loading modules.\n assert(jobAddress && (sliceNumber || sliceNumber === 0));\n // Send a work emit message from the sandbox to the event router\n // !authorizationMessage <==> sliceNumber === 0.\n let authorizationMessage;\n try {\n // Sometimes a sliceNumber===0 workEmit comes in before the client bundle is properly loaded.\n // Also happens with minor dcp-client version mismatches.\n authorizationMessage = sandbox.slice.getAuthorizationMessage();\n } catch(e) {\n authorizationMessage = null;\n }\n\n if (!authorizationMessage)\n {\n console.warn(`workEmit: missing authorization message for job ${jobAddress}, slice: ${sliceNumber}`);\n return Promise.resolve();\n }\n \n let workEmitPayload = {\n eventName,\n payload,\n job: jobAddress,\n slice: sliceNumber,\n worker: this.workerOpaqueId,\n authorizationMessage,\n }\n \n const workEmitPromise = this.eventRouterConnection.send('workEmit', workEmitPayload).catch(error => {\n debugging('supervisor') && console.warn(`workEmit: unable to send ${eventName} for slice ${sliceNumber} of job ${jobAddress}: ${error.message}.\\nTrying again on a new connection.`);\n this.eventRouterMessageQueue.push({ operation: 'workEmit', data: workEmitPayload })\n this.eventRouterConnection.close();\n if (Supervisor.debugBuild)\n console.error('workEmit error:', error);\n });\n\n if (Supervisor.debugBuild) {\n workEmitPromise.then(result => {\n if (!result || !result.success)\n console.warn('workEmit: event router did not accept event', result);\n });\n }\n }\n });\n\n // When any sbx completes, \n sandbox.addListener('complete', () => {\n this.watchdog();\n });\n\n sandbox.on('sandboxError', (error) => handleSandboxError(this, sandbox, error));\n \n sandbox.on('rejectedWorkMetrics', (data) =>{\n function updateRejectedMetrics(report) {\n ['total', 'CPU', 'webGL'].forEach((key) => {\n if (report[key]) sandbox.slice.rejectedTimeReport[key] += report[key];\n })\n }\n \n // If the slice already has rejected metrics, add this data to it. If not, assign this data to slices rejected metrics property\n if (sandbox.slice) {\n (sandbox.slice.rejectedTimeReport) ? updateRejectedMetrics(data.timeReport) : sandbox.slice.rejectedTimeReport = data.timeReport;\n }\n })\n \n // If the sandbox terminated and we are not shutting down, then should return all work which is currently\n // not being computed if all sandboxes are dead and the attempt to create a new one fails.\n sandbox.on('terminated',async () => {\n if (this.sandboxes.length > 0) {\n let terminatedSandboxes = this.sandboxes.filter(sbx => sbx.isTerminated);\n if (terminatedSandboxes.length === this.sandboxes.length) {\n debugging('supervisor') && console.debug(`readySandboxes: Create 1 sandbox in the sandbox-terminated-handler, total sandboxes ${this.sandboxes.length}, matching ${this.matching}, fetching ${this.isFetchingNewWork}`);\n await this.readySandboxes(1);\n \n // If we cannot create a new sandbox, that probably means we're on a screensaver worker\n // and the screensaver is down. So return the slices to the scheduler.\n if (this.sandboxes.length !== terminatedSandboxes.length + 1) {\n this.returnSlices(this.queuedSlices).then(() => {\n this.queuedSlices.length = 0;\n });\n }\n }\n }\n })\n\n const delayMs =\n 1000 *\n (tuning.minSandboxStartDelay +\n Math.random() *\n (tuning.maxSandboxStartDelay - tuning.minSandboxStartDelay));\n \n sandboxStartPromises.push(\n sandbox\n .start(delayMs)\n .then(() => {\n if (!allocateLocalSandboxes) this.readiedSandboxes.push(sandbox);\n this.sandboxes.push(sandbox);\n sandboxes.push(sandbox);\n }).catch((err) => {\n errors.push(err);\n this.returnSandbox(sandbox);\n if (err.code === 'ENOWORKER') {\n throw new DCPError(\"Cannot use localExec without dcp-worker installed. Use the command 'npm install dcp-worker' to install the neccessary modules.\", 'ENOWORKER');\n }\n }));\n }\n \n await Promise.all(sandboxStartPromises);\n\n if (errors.length) {\n console.warn(`Failed to ready ${errors.length} of ${numSandboxes} sandboxes.`, errors);\n throw new Error('Failed to ready sandboxes.');\n }\n\n debugging('supervisor') && console.log(`readySandboxes: Readied ${sandboxes.length} sandboxes ${JSON.stringify(sandboxes.map(sandbox => sandbox.id))}`);\n \n return sandboxes;\n }\n\n /**\n * Accepts a sandbox after it has finished working or encounters an error.\n * If the sandbox was terminated or if \"!slice || slice.failed\" then\n * the sandbox will be removed from the sandboxes array and terminated if necessary.\n * Otherwise it will try to distribute a slice to the sandbox immediately.\n *\n * @param {Sandbox} sandbox - the sandbox to return\n * @param {Slice} [slice] - the slice just worked on; !slice => terminate\n * @param {boolean} [verifySandboxIsNotTerminated=true] - if true, check sandbox is not already terminated\n */\n returnSandbox (sandbox, slice, verifySandboxIsNotTerminated=true) {\n if (!slice || slice.failed || sandbox.isTerminated) {\n \n this.removeSandbox(sandbox);\n \n if (!sandbox.isTerminated) {\n debugging('supervisor') && console.log(`Supervisor.returnSandbox: Terminating ${sandbox.identifier}${slice ? `~${slice.identifier}` : ''}, # of sandboxes ${this.sandboxes.length}`);\n sandbox.terminate(false);\n } else {\n debugging('supervisor') && console.log(`Supervisor.returnSandbox: Already terminated ${sandbox.identifier}${slice ? `~${slice.identifier}` : ''}, # of sandboxes ${this.sandboxes.length}`);\n // XXXpfr: April 13, 2022\n // I'm trying to understand and control when sandboxes get terminated.\n // Because of races and random worker crashes, it is impossible to not try to terminate a sandbox more than once.\n // But at some places where returnSandbox is we shouldn't see this behavior, hence this exception.\n // If I don't fix this is the next 30 days or I forget, please delete this exception.\n if (false)\n {}\n }\n }\n }\n\n /**\n * Terminates sandboxes, in order of creation, when the total started sandboxes exceeds the total allowed sandboxes.\n *\n * @returns {Promise<void>}\n */\n pruneSandboxes () {\n let numOver = this.sandboxes.length - (dcpConfig.worker.maxAllowedSandboxes + this.maxWorkingSandboxes);\n if (numOver <= 0) return;\n \n // Don't kill readied sandboxes while creating readied sandboxes.\n for (let index = 0; index < this.readiedSandboxes.length; ) {\n const sandbox = this.readiedSandboxes[index];\n // If the sandbox is allocated, advance to the next one in the list.\n if (sandbox.allocated) {\n index++;\n continue;\n }\n // Otherwise, remove this sandbox but look at the same array index in the next loop.\n debugging('supervisor') && console.log(`pruneSandboxes: Terminating readied sandbox ${sandbox.identifier}`);\n this.readiedSandboxes.splice(index, 1);\n this.returnSandbox(sandbox);\n\n if (--numOver <= 0) break;\n }\n\n if (numOver <= 0) return;\n for (let index = 0; index < this.assignedSandboxes.length; ) {\n const sandbox = this.assignedSandboxes[index];\n // If the sandbox is allocated, advance to the next one in the list.\n if (sandbox.allocated) {\n index++;\n continue;\n }\n // Otherwise, remove this sandbox but look at the same array index in the next loop.\n debugging('supervisor') && console.log(`pruneSandboxes: Terminating assigned sandbox ${sandbox.identifier}`);\n this.assignedSandboxes.splice(index, 1);\n this.returnSandbox(sandbox);\n\n if (--numOver <= 0) break;\n }\n }\n \n /**\n * Basic watch dog to check if there are idle sandboxes and\n * attempts to nudge the supervisor to feed them work.\n *\n * Run in an interval created in @constructor .\n * @returns {Promise<void>}\n */\n async watchdog () {\n if (!this.watchdogState)\n this.watchdogState = {};\n\n // Every 5 minutes, report progress of all working slices to the scheduler\n if (Date.now() > ((this.lastProgressReport || 0) + sandboxTuning.progressReportInterval)) {\n // console.log('454: Assembling progress update...');\n this.lastProgressReport = Date.now();\n\n //\n // Note: this.slices is the disjoint union of:\n // this.allocatedSlices, \n // this.queuedSlices, \n // this.slices.filter(slice => !slice.isUnassigned) .\n // When a slice is not in these 3 arrays, the slice is lost.\n //\n \n const currentLostSlices = this.slices.filter(slice => slice.isUnassigned \n && this.queuedSlices.indexOf(slice) === -1\n && this.allocatedSlices.indexOf(slice) === -1);\n\n if (currentLostSlices.length > 0) {\n this.lostSlices.push(...currentLostSlices);\n // Try to recover.\n // Needs more work and testing.\n // Test when we can come up with a decent lost slice repro case.\n // --> this.queuedSlices.push(...currentLostSlices);\n }\n\n if (this.lostSlices.length > 0) {\n if (true) { // Keep this on for awhile, until we know lost slices aren't happening.\n console.warn('Supervisor.watchdog: Found lost slices!');\n for (const slice of this.lostSlices)\n console.warn('\\t', slice.identifier);\n }\n this.lostSlices = this.lostSlices.filter(slice => slice.isUnassigned);\n }\n\n const slices = [];\n this.queuedSlices.forEach(slice => {\n assert(slice && slice.sliceNumber > 0);\n addToSlicePayload(slices, slice, sliceStatus.scheduled);\n });\n\n this.allocatedSlices.forEach(slice => {\n assert(slice && slice.sliceNumber > 0);\n addToSlicePayload(slices, slice, 'progress'); // Beacon.\n });\n\n if (slices.length) {\n // console.log('471: sending progress update...');\n const progressReportPayload = {\n worker: this.workerOpaqueId,\n slices,\n };\n\n this.resultSubmitterConnection.send('status', progressReportPayload)\n .catch(error => {\n debugging('supervisor') && console.error('479: Failed to send status update:', error/*.message*/);\n this.resultSubmitterMessageQueue.push({ operation: 'status', data: progressReportPayload })\n this.resultSubmitterConnection.close();\n });\n }\n }\n\n if (this.worker.working) {\n if (this.unallocatedSpace > 0) {\n await this.work().catch(err => {\n if (!this.watchdogState[err.code || '0'])\n this.watchdogState[err.code || '0'] = 0;\n if (Date.now() - this.watchdogState[err.code || '0'] > ((dcpConfig.worker.watchdogLogInterval * timeDilation || 120) * 1000))\n console.error('301: Failed to start work:', err);\n this.watchdogState[err.code || '0'] = Date.now();\n });\n }\n\n this.pruneSandboxes();\n }\n }\n\n /**\n * Gets the logical and physical number of cores and also\n * the total number of sandboxes the worker is allowed to run\n *\n */\n getStatisticsCPU() {\n if (DCP_ENV.isBrowserPlatform) {\n return {\n worker: this.workerOpaqueId,\n lCores: window.navigator.hardwareConcurrency,\n pCores: dcpConfig.worker.pCores || window.navigator.hardwareConcurrency,\n sandbox: this.maxWorkingSandboxes\n }\n }\n\n return {\n worker: this.workerOpaqueId,\n lCores: requireNative('os').cpus().length,\n pCores: requireNative('physical-cpu-count'),\n sandbox: this.maxWorkingSandboxes\n }\n }\n\n /**\n * Returns the number of unallocated sandbox slots to send to fetchTask.\n *\n * @returns {number}\n */\n numberOfAvailableSandboxSlots() {\n let numCores;\n if (this.options.priorityOnly && this.options.jobAddresses.length === 0) {\n numCores = 0;\n } else if (this.queuedSlices.length > 1) {\n // We have slices queued, no need to fetch\n numCores = 0;\n } else {\n // The queue is almost empty (there may be 0 or 1 element), fetch a full task.\n // The task is full, in the sense that it will contain slices whose\n // aggregate execution time is this.maxWorkingSandboxes * 5-minutes.\n // However, there can only be this.unallocatedSpace # of long slices.\n // Thus we need to know whether the last slice in this.queuedSlices is long or not.\n // (A long slice has estimated execution time >= 5-minutes.)\n const longSliceCount = (this.queuedSlices.length > 0 && this.queuedSlices[0].isLongSlice) ? 1 : 0;\n numCores = this.unallocatedSpace - longSliceCount;\n }\n return numCores;\n }\n\n /**\n * Call to start doing work on the network.\n * This is the one place where requests to fetch new slices are made.\n * After the initial slices are fetched it calls this.distributeQueuedSlices.\n *\n * @returns {Promise<void>}, unallocatedSpace ${this.unallocatedSpace}\n */\n async work()\n {\n // When inside matchSlicesWithSandboxes, don't reenter Supervisor.work to fetch new work or create new sandboxes.\n if (this.matching) {\n // Interesting and noisy.\n // debugging('supervisor') && console.log(`Supervisor.work: Do not interleave work, fetch or matching slices with sandboxes: queuedSlices ${this.queuedSlices.length}, unallocatedSpace ${this.unallocatedSpace}, matching ${this.matching}, fetching ${this.isFetchingNewWork}`);\n return Promise.resolve();\n }\n\n await this.setDefaultIdentityKeystore();\n\n // Instantiate connections that don't exist.\n this.instantiateAllConnections();\n\n const numCores = this.numberOfAvailableSandboxSlots();\n\n debugging() && console.log(`Supervisor.work: Try to get ${numCores} slices in working sandboxes, unallocatedSpace ${this.unallocatedSpace}, queued slices ${this.queuedSlices.length}, # of sandboxes ${this.sandboxes.length}, matching ${this.matching}, fetching: ${this.isFetchingNewWork}`);\n \n // Fetch a new task if we have no more slices queued, then start workers\n try {\n if (numCores > 0 && !this.isFetchingNewWork) {\n this.isFetchingNewWork = true;\n\n /**\n * This will only ready sandboxes up to a total count of\n * maxWorkingSandboxes (in any state). It is not possible to know the\n * actual number of sandboxes required until we have the slices because we\n * may have sandboxes assigned for the slice's job already.\n *\n * If the evaluator cannot start (ie. if the evalServer is not running),\n * then the while loop will keep retrying until the evalServer comes online\n */\n if (this.maxWorkingSandboxes > this.sandboxes.length) {\n // Note: The old technique had \n // while (this.maxWorkingSandboxes > this.sandboxes.length) {....\n // and sometimes we'd get far too many sandboxes, because it would keep looping while waiting for\n // this.readySandboxes(this.maxWorkingSandboxes - this.sandboxes.length);\n // to construct the rest of the sandboxes. The fix is to only loop when the 1st \n // await this.readySandboxes(1) \n // is failing.\n let needFirstSandbox = true;\n while (needFirstSandbox) {\n debugging('supervisor') && console.log(`Supervisor.work: ready 1 sandbox, # of sandboxes ${this.sandboxes.length}, matching ${this.matching}, fetching ${this.isFetchingNewWork}`);\n await this.readySandboxes(1)\n .then(() => {\n debugging('supervisor') && console.log(`Supervisor.work: ready ${this.maxWorkingSandboxes - this.sandboxes.length} sandbox(es), # of sandboxes ${this.sandboxes.length}, matching ${this.matching}, fetching ${this.isFetchingNewWork}`);\n this.readySandboxes(this.maxWorkingSandboxes - this.sandboxes.length);\n needFirstSandbox = false;\n }).catch(error => {\n console.warn('906: failed to ready sandboxes; will retry', error.code, error.message);\n });\n }\n }\n\n /**\n * Temporary change: Assign the capabilities of one of readied sandboxes\n * before fetching slices from the scheduler.\n *\n * TODO: Remove this once fetchTask uses the capabilities of every\n * sandbox to fetch slices.\n */\n if (!this.capabilities) {\n this.capabilities = this.sandboxes[0].capabilities;\n this.emit('capabilitiesCalculated', this.capabilities);\n }\n\n if (DCP_ENV.isBrowserPlatform && this.capabilities.browser)\n this.capabilities.browser.chrome = DCP_ENV.isBrowserChrome;\n\n const fetchTimeout = setTimeout(() => {\n console.warn(`679: Fetch exceeded timeout, will reconnect at next watchdog interval`);\n \n this.taskDistributorConnection.close('Fetch timed out', Math.random() > 0.5).catch(error => {\n console.error(`931: Failed to close task-distributor connection`, error);\n });\n this.resultSubmitterConnection.close('Fetch timed out', Math.random() > 0.5).catch(error => {\n console.error(`920: Failed to close result-submitter connection`, error);\n });\n this.isFetchingNewWork = false;\n this.instantiateAllConnections();\n }, 3 * 60 * 1000); // max out at 3 minutes to fetch\n\n // ensure result submitter and task distributor connections before fetching tasks\n try\n {\n await this.resultSubmitterConnection.keepalive();\n await this.taskDistributorConnection.keepalive();\n }\n catch (e)\n {\n console.error('Failed to connect to result submitter, refusing to fetch slices. Will try again at next fetch cycle.')\n debugging('supervisor') && console.log(`Error: ${e}`);\n this.isFetchingNewWork = false; // <-- done in the `finally` block, below\n clearTimeout(fetchTimeout);\n this.taskDistributorConnection.close('Failed to connect to result-submitter', true).catch(error => {\n console.error(`939: Failed to close task-distributor connection`, error);\n });\n this.resultSubmitterConnection.close('Failed to connect to result-submitter', true).catch(error => {\n console.error(`942: Failed to close result-submitter connection`, error);\n });\n return Promise.resolve();\n }\n await this.fetchTask(numCores).finally(() => {\n clearTimeout(fetchTimeout);\n this.isFetchingNewWork = false;\n });\n }\n\n this.distributeQueuedSlices().then(() => debugging('supervisor') && 'supervisor: finished distributeQueuedSlices()').catch((e) => {\n // We should never get here, because distributeQueuedSlices was changed\n // to try to catch everything and return slices and sandboxes.\n // If we do catch here it may mean a slice was lost. \n console.error('Supervisor.work catch handler for distributeQueuedSlices.', e);\n });\n // No catch(), because it will bubble outward to the caller\n } finally {\n }\n }\n\n /**\n * Generate the workerComputeGroups property of the requestTask message. \n * \n * Concatenate the compute groups object from dcpConfig with the list of compute groups\n * from the supervisor, and remove the public group if accidentally present. Finally,\n * we transform joinSecrets/joinHash into joinHashHash for secure transmission.\n *\n * @note computeGroup objects with joinSecrets are mutated to record their hashes. This\n * affects the supervisor options and dcpConfig. Re-adding a joinSecret property\n * to one of these will cause the hash to be recomputed.\n */\n generateWorkerComputeGroups()\n {\n var computeGroups = Object.values(dcpConfig.worker.computeGroups || {});\n if (this.options.computeGroups)\n computeGroups = computeGroups.concat(this.options.computeGroups);\n computeGroups = computeGroups.filter(group => group.id !== constants.computeGroups.public.id);\n const hashedComputeGroups = [];\n for (const group of computeGroups)\n {\n const groupCopy = Object.assign({}, group);\n if ((group.joinSecret || group.joinHash) && (!group.joinHashHash || this.lastDcpsid !== this.taskDistributorConnection.dcpsid))\n {\n let joinHash;\n if (group.joinHash) {\n joinHash = group.joinHash.replace(/\\s+/g, ''); // strip whitespace\n } else {\n joinHash = calculateJoinHash(groupCopy);\n } \n\n groupCopy.joinHashHash = hash.calculate(hash.eh1, joinHash, this.taskDistributorConnection.dcpsid);\n delete groupCopy.joinSecret;\n delete groupCopy.joinHash;\n debugging('computeGroups') && console.debug(`Calculated joinHash=${joinHash} for`, groupCopy);\n }\n hashedComputeGroups.push(groupCopy);\n }\n this.lastDcpsid = this.taskDistributorConnection.dcpsid;\n debugging('computeGroups') && console.debug('Requesting ', computeGroups.length, 'non-public groups for session', this.lastDcpsid);\n return hashedComputeGroups;\n }\n\n /**\n * Remove all unreferenced jobs in `this.cache`.\n *\n * @param {any[]} newJobs - Jobs that should not be removed from\n * `this.cache`.\n */\n cleanJobCache(newJobs = []) {\n /* Delete all jobs in the supervisorCache that are not represented in this newJobs,\n * or in this.queuedSlices, or there is no sandbox assigned to these jobs.\n * Note: There can easily be 200+ places to check; using a lookup structure to maintain O(n).\n */\n if (this.cache.jobs.length > 0) {\n const jobAddressMap = {};\n Object.keys(newJobs).forEach(jobAddress => { jobAddressMap[jobAddress] = 1; });\n this.slices.forEach(slice => { if (!jobAddressMap[slice.jobAddress]) jobAddressMap[slice.jobAddress] = 1; });\n this.cache.jobs.forEach(jobAddress => {\n if (!jobAddressMap[jobAddress]) {\n this.cache.remove('job', jobAddress);\n // Remove and return the corresponding sandboxes from this.sandboxes.\n const deadSandboxes = this.sandboxes.filter(sb => sb.jobAddress === jobAddress);\n if (deadSandboxes.length > 0) {\n deadSandboxes.forEach(sandbox => { this.returnSandbox(sandbox); });\n debugging('supervisor') && console.log(`Supervisor.fetchTask: Deleting job ${jobAddress} from cache and assigned sandboxes ${deadSandboxes.map(s => s.id)}, # of sandboxes ${this.sandboxes.length}.`);\n }\n }\n });\n }\n }\n\n /**\n * Fetches a task, which contains job information and slices for sandboxes and\n * manages events related to fetching tasks so the UI can more clearly display\n * to user what is actually happening.\n * @param {number} numCores\n * @returns {Promise<void>} The requestTask request, resolve on success, rejects otherwise.\n * @emits Supervisor#fetchingTask\n * @emits Supervisor#fetchedTask\n */\n async fetchTask(numCores) {\n\n // Don't reenter\n if (this.matching || numCores <= 0) {\n // Interesting and noisy.\n debugging('supervisor') && console.log(`Supervisor.fetchTask: Do not nest work, fetch or matching slices with sandboxes: queuedSlices ${this.queuedSlices.length}, unallocatedSpace ${this.unallocatedSpace}, matching ${this.matching}, fetching ${this.isFetchingNewWork}, numCores ${numCores}`);\n return Promise.resolve();\n }\n\n //\n // Oversubscription mitigation.\n // Update when there are less available sandbox slots than numCores.\n const checkNumCores = this.numberOfAvailableSandboxSlots();\n if (numCores > checkNumCores) numCores = checkNumCores;\n if (numCores <= 0) return Promise.resolve();\n\n this.emit('fetchingTask');\n debugging('supervisor') && console.debug('supervisor: fetching task');\n const requestPayload = {\n numCores,\n coreStats: this.getStatisticsCPU(),\n numGPUs: this.defaultMaxGPUs,\n capabilities: this.capabilities,\n paymentAddress: this.paymentAddress,\n jobAddresses: this.options.jobAddresses || [], // force array; when set, only fetches slices for these jobs\n localExec: this.options.localExec,\n workerComputeGroups: this.generateWorkerComputeGroups(),\n minimumWage: dcpConfig.worker.minimumWage || this.options.minimumWage,\n readyJobs: [ /* list of jobs addresses XXXwg */ ],\n previouslyWorkedJobs: this.ringBufferofJobs.buf, //Only discrete jobs\n rejectedJobs: this.rejectedJobs,\n };\n // workers should be part of the public compute group by default\n if (!booley(dcpConfig.worker.leavePublicGroup) && !booley(this.options.leavePublicGroup) && (!requestPayload.localExec))\n requestPayload.workerComputeGroups.push(constants.computeGroups.public);\n debugging('computeGroups') && console.log(`Fetching work for ${requestPayload.workerComputeGroups.length} ComputeGroups: `, requestPayload.workerComputeGroups);\n debugging('supervisor') && console.log(`fetchTask wants ${numCores} slice(s), unallocatedSpace ${this.unallocatedSpace}, queuedSlices ${this.queuedSlices.length}`);\n try {\n debugging('requestTask') && console.debug('fetchTask: requestPayload', requestPayload);\n\n let result = await this.taskDistributorConnection.send('requestTask', requestPayload).catch((error) => {\n debugging('supervisor') && console.error(`Unable to request task from scheduler: ${error}. Will try again on a new connection.`);\n this.taskDistributorConnection.close(error, true);\n throw error; /* caught below */\n });\n let responsePayload = result.payload; \n\n if (!result.success) {\n debugging() && console.log('Task fetch failure; request=', requestPayload);\n debugging() && console.log('Task fetch failure; response=', result.payload);\n throw new DCPError('Unable to fetch task for worker', responsePayload);\n }\n\n const sliceCount = responsePayload.body.task.length || 0;\n\n /**\n * The fetchedTask event fires when the supervisor has finished trying to\n * fetch work from the scheduler (task-manager). The data emitted is the\n * number of new slices to work on in the fetched task.\n *\n * @event Supervisor#fetchedTask\n * @type {number}\n */\n this.emit('fetchedTask', sliceCount);\n\n if (sliceCount < 1) {\n return Promise.resolve();\n }\n\n /**\n * DCP-1698 Send auth msg with tasks to worker, then validate authority of worker to send slice info back to scheduler.\n * payload structure: { owner: this.address, signature: signature, auth: messageLightWeight, body: messageBody };\n * messageLightWeight: { workerId: worker, jobSlices, schedulerId, jobCommissions }\n * messageBody: { newJobs: await getNewJobsForTask(dbScheduler, task, request), task }\n */\n const { body, ...authorizationMessage } = responsePayload;\n const { newJobs, task } = body;\n assert(newJobs); // It should not be possible to have !newJobs -- we throw on !success.\n \n /*\n * Ensure all jobs received from the scheduler are:\n * 1. If we have specified specific jobs the worker may work on, the received jobs are in the specified job list\n * 2. If we are in localExec, at most 1 unique job type was received (since localExec workers are designated for only\n * one job)\n * If the received jobs are not within these parameters, stop the worker since the scheduler cannot be trusted at that point.\n */\n if ((this.options.jobAddresses.length && !Object.keys(newJobs).every((ele) => this.options.jobAddresses.includes(ele)))\n || (this.options.localExec && Object.keys(newJobs).length > 1))\n {\n console.error(\"Worker received slices it shouldn't have. Rejecting the work and stopping.\");\n process.exit(1);\n }\n\n debugging() && console.log(`Supervisor.fetchTask: task: ${task.length}/${numCores}, jobs: ${Object.keys(newJobs).length}, authSlices: ${compressJobMap(authorizationMessage.auth.authSlices, true /* skipFirst*/, this.addressTruncationLength /* digits*/)}`);\n // Delete all jobs in the supervisorCache that are not represented in this task,\n // or in this.queuedSlices, or there is no sandbox assigned to these jobs.\n this.cleanJobCache(newJobs);\n\n for (const jobAddress of Object.keys(newJobs))\n if (!this.cache.cache.job[jobAddress])\n this.cache.store('job', jobAddress, newJobs[jobAddress]);\n\n // Memoize authMessage onto the Slice object, this should\n // follow it for its entire life in the worker.\n const tmpQueuedSlices = task.map(taskElement => new Slice(taskElement, authorizationMessage));\n\n // Make sure old stuff is up front.\n // matchSlicesWithSandboxes dequeues this.queuedSlices as follows:\n // slicesToMatch = this.queuedSlices.slice(0, numCores);\n this.slices.push(...tmpQueuedSlices);\n this.queuedSlices.push(...tmpQueuedSlices);\n \n // Populating the ring buffer based on job's discrete property \n Object.values(newJobs).forEach(job => {\n if(job.requirements.discrete && this.ringBufferofJobs.find(element => element === job.address) === undefined) {\n this.ringBufferofJobs.push(job.address);\n }\n });\n \n } catch (error) {\n this.emit('fetchTaskFailed', error);\n debugging('supervisor') && console.debug(`Supervisor.fetchTask failed!: error: ${error}`);\n }\n }\n\n /**\n * For each slice in this.queuedSlices, match with a sandbox in the following order:\n * 1. Try to find an already assigned sandbox in this.assignedSandboxes for the slice's job.\n * 2. Find a ready sandbox in this.readiedSandboxes that is unassigned.\n * 3. Ready a new sandbox and use that.\n *\n * Take great care in assuring sandboxes and slices are uniquely associated, viz.,\n * a given slice cannot be associated with multiple sandboxes and a given sandbox cannot be associated with multiple slices.\n * The lack of such uniqueness has been the root cause of several difficult bugs.\n *\n * Note: When a sandbox is paired with a slice, execution is pending and sandbox.allocated=true and\n * sandbox.slice=slice and sandbox.jobAddress=slice.jobAddress. This is what 'allocated' means.\n * Immediately upon the exit of sandbox.work, sandbox.allocated=false is set and if an exception\n * wasn't thrown, the paired slice is placed in this.assignedSandboxes.\n * Thus from the pov of supervisor, this.allocatedSandboxes is deterministic and this.workingSandboxes is not.\n * Please try to not use this.workingSandboxes. It is deprecated.\n *\n * The input is numCores, this,queuedSlices, this.assignedSandboxes and this.readiedSandboxes.\n * If there are not enough sandboxes, new readied sandboxes will be created using\n * await this.readySandboxes(...)\n * And it is this await boundary that has caused many bugs.\n * We try not to make assumptions about non-local state across the await boundary.\n *\n * @param {number} numCores - The number of available sandbox slots.\n * @param {boolean} [throwExceptions=true] - Whether to throw exceptions when checking for sanity.\n * @returns {Promise<SandboxSlice[]>} Returns SandboxSlice[], may have length zero.\n */\n async matchSlicesWithSandboxes (numCores, throwExceptions = true) {\n\n const sandboxSlices = [];\n if (this.queuedSlices.length === 0 || this.matching || numCores <= 0) {\n // Interesting and noisy.\n // debugging('supervisor') && console.log(`Supervisor.matchSlicesWithSandboxes: Do not nest work, fetch or matching slices with sandboxes: queuedSlices ${this.queuedSlices.length}, unallocatedSpace ${this.unallocatedSpace}, matching ${this.matching}, fetching ${this.isFetchingNewWork}, numCores ${numCores}`);\n return sandboxSlices;\n }\n\n //\n // Oversubscription mitigation.\n // Update when there are less available sandbox slots than numCores.\n // We cannot use this.unallocatedSpace here because its value is artificially low or zero, because in\n // this.distributedQueuedSlices we use the pseudo-mutex trick: this.acquire(howManySandboxSlotsToReserve)/this.release().\n // Note: Do not use this.numberOfCoresReserved outside of a function locked with this.acquire(howManySandboxSlotsToReserve) .\n const checkNumCores = this.numberOfCoresReserved; // # of locked sandbox slots.\n if (numCores > checkNumCores) numCores = checkNumCores;\n if (numCores <= 0) return sandboxSlices;\n\n // Don't ask for more than we have.\n if (numCores > this.queuedSlices.length)\n numCores = this.queuedSlices.length;\n\n debugging('supervisor') && console.log(`matchSlicesWithSandboxes: numCores ${numCores}, queued slices ${this.queuedSlices.length}: assigned ${this.assignedSandboxes.length}, readied ${this.readiedSandboxes.length}, unallocated ${this.unallocatedSpace}, # of sandboxes: ${this.sandboxes.length}`);\n\n if (debugging('supervisor')) {\n dumpSlicesIfNotUnique(this.queuedSlices, 'Warning: this.queuedSlices slices are not unique -- this is ok when slice is rescheduled.');\n dumpSandboxesIfNotUnique(this.readiedSandboxes, 'Warning: this.readiedSandboxes sandboxes are not unique!');\n dumpSandboxesIfNotUnique(this.assignedSandboxes, 'Warning: this.assignedSandboxes sandboxes are not unique!');\n }\n\n // Three functions to validate slice and sandbox.\n function checkSlice(slice, checkAllocated=true) {\n if (!slice.isUnassigned) throw new DCPError(`Slice must be unassigned: ${slice.identifier}`);\n if (checkAllocated && slice.allocated) throw new DCPError(`Slice must not already be allocated: ${slice.identifier}`);\n }\n function checkSandbox(sandbox, isAssigned) {\n if (sandbox.allocated) throw new DCPError(`Assigned sandbox must not be already allocated: ${sandbox.identifier}`);\n if (isAssigned && !sandbox.isAssigned) throw new DCPError(`Assigned sandbox is not marked as assigned: ${sandbox.identifier}`);\n if (!isAssigned && !sandbox.isReadyForAssign) throw new DCPError(`Readied sandbox is not marked as ready for assign: ${sandbox.identifier}`);\n }\n\n // Sanity checks.\n if (throwExceptions) {\n this.assignedSandboxes.forEach(sandbox => { checkSandbox(sandbox, true /* isAssigned*/); });\n this.readiedSandboxes.forEach(sandbox => { checkSandbox(sandbox, false /* isAssigned*/); });\n this.queuedSlices.forEach(slice => { checkSlice(slice); });\n } else {\n this.assignedSandboxes = this.assignedSandboxes.filter(sandbox => !sandbox.allocated && sandbox.isAssigned);\n this.readiedSandboxes = this.readiedSandboxes.filter(sandbox => !sandbox.allocated && sandbox.isReadyForAssign);\n this.queuedSlices = this.queuedSlices.filter(slice => !slice.allocated && slice.isUnassigned);\n }\n\n const sandboxKind = {\n assigned: 0,\n ready: 1,\n new: 2,\n };\n\n const ceci = this;\n /**\n * Auxiliary function to pair a sandbox with a slice and mark the sandbox as allocated.\n * An allocated sandbox is reserved and will not be released until the slice completes execution on the sandbox.\n *\n * @param {Sandbox} sandbox\n * @param {Slice} slice\n * @param {number} kind\n */\n function pair(sandbox, slice, kind) {\n checkSandbox(sandbox, kind === sandboxKind.assigned);\n checkSlice(slice, kind === sandboxKind.assigned);\n slice.allocated = true;\n sandbox.allocated = true;\n sandbox.jobAddress = slice.jobAddress; // So we can know which jobs to not delete from this.cache .\n sandbox.slice = slice;\n sandboxSlices.push({ sandbox, slice });\n if (Supervisor.sliceTiming) slice['pairingDelta'] = Date.now();\n if (debugging('supervisor')) {\n let fragment = 'New readied';\n if (kind === sandboxKind.assigned) fragment = 'Assigned';\n else if (kind === sandboxKind.ready) fragment = 'Readied';\n console.log(`matchSlicesWithSandboxes.pair: ${fragment} sandbox matched ${ceci.dumpSandboxAndSlice(sandbox, slice)}`);\n }\n }\n\n // These three arrays are used to track/store slices and sandboxes,\n // so that when an exception occurs, the following arrays are restored:\n // this.queuedSlices, this.assignedSandboxes, this.realizedSandboxes.\n let slicesToMatch = [];\n let trackAssignedSandboxes = [];\n let trackReadiedSandboxes = [];\n try\n {\n this.matching = true;\n\n let assignedCounter = 0; // How many assigned sandboxes are being used.\n let readyCounter = 0; // How many sandboxes used from the existing this.readiedSandboxes.\n let newCounter = 0; // How many sandboxes that needed to be newly created.\n\n //\n // The Ideas:\n // 1) We match each slice with a sandbox. First we match with assigned sandboxes in the order\n // that they appear in this.queuedSlices. Then we match in-order with existing this.readiedSandboxes\n // Then we match in-order with new new readied sandboxes created through\n // await this.readySandboxes(newCounter, true /* allocateLocalSandboxes*/);\n // This allows us to try different orderings of execution of slices. E.g. Wes suggested\n // trying to execute slices from different jobs with maximal job diversity -- specifically\n // if there are 3 jobs j1,j2,j3, with slices s11, s12 from j1, s21, s22, s23 from j2 and\n // s31, s32 from j3, then we try to schedule, in order s11, s21, s31, s12, s22, s32, s23.\n //\n // 2) Before matching slices with sandboxes, we allocate available assigned and readied sandboxes\n // and if more are needed then we create and allocate new ones.\n //\n // 3) Finally we match slices with sandboxes and return an array of sandboxSlice pairs.\n //\n // Note: The ordering of sandboxSlices only partially corresponds to the order of this.queuedSlices.\n // It's easy to do. When pairing with assigned sandboxes, any slice in this.queuedSlices which doesn't\n // have an assigned sandbox, will add null to the sandboxSlices array. Then when pairing with readied sandboxes,\n // we fill-in the null entries in the sandboxSlices array.\n //\n /** XXXpfr @todo When it is needed, fix the ordering as described above. */\n\n // Get the slices that are being matched.\n slicesToMatch = this.queuedSlices.slice(0, numCores);\n this.queuedSlices = this.queuedSlices.slice(numCores);\n\n debugging('supervisor') && console.log(`matchSlicesWithSandboxes: slicesToMatch ${this.dumpSlices(slicesToMatch)}`);\n\n // Create object map: jobAddress -> sandboxes with sandboxes.jobAddress === jobAddress .\n const jobSandboxMap = toJobMap(this.assignedSandboxes, sandbox => sandbox);\n \n // Create array to hold slices which do not have assigned sandboxes.\n // These slices will need to be paired with existing and possibly new readied sandboxes.\n // Specifically, the sandboxes from existing this.readiedSandboxes and new sandboxes\n // created through await this.readySandboxes(newCounter, true /* allocateLocalSandboxes*/);\n const slicesThatNeedSandboxes = [];\n\n // Pair assigned sandboxes with slices.\n for (const slice of slicesToMatch) {\n const assigned = jobSandboxMap[slice.jobAddress];\n if (assigned && assigned.length > 0) {\n // Pair.\n const sandbox = assigned.pop();\n pair(sandbox, slice, sandboxKind.assigned);\n this.removeElement(this.assignedSandboxes, sandbox);\n // Track.\n trackAssignedSandboxes.push(sandbox);\n assignedCounter++;\n } else {\n // Don't lose track of these slices.\n slice.allocated = true;\n slicesThatNeedSandboxes.push(slice);\n }\n }\n\n // Pair readied sandboxes with slices.\n readyCounter = Math.min(slicesThatNeedSandboxes.length, this.readiedSandboxes.length);\n newCounter = slicesThatNeedSandboxes.length - readyCounter;\n // Track.\n trackReadiedSandboxes = this.readiedSandboxes.slice(0, readyCounter);\n this.readiedSandboxes = this.readiedSandboxes.slice(readyCounter);\n for (const sandbox of trackReadiedSandboxes) {\n // Pair.\n const slice = slicesThatNeedSandboxes.pop();\n pair(sandbox, slice, sandboxKind.ready);\n }\n \n debugging('supervisor') && console.log(`matchSlicesWithSandboxes: assignedCounter ${assignedCounter}, readyCounter ${readyCounter}, newCounter ${newCounter}, numCores ${numCores}`)\n\n // Validate algorithm consistency.\n if (Supervisor.debugBuild && assignedCounter + readyCounter + newCounter !== numCores) {\n // Structured assert.\n throw new DCPError(`matchSlicesWithSandboxes: Algorithm is corrupt ${assignedCounter} + ${readyCounter} + ${newCounter} !== ${numCores}`);\n }\n\n // Here is an await boundary.\n // Accessing non-local data across an await boundary may result in the unexpected.\n\n // Create new readied sandboxes to associate with slicesThatNeedSandboxes.\n if (newCounter > 0) {\n // When allocateLocalSandboxes is true, this.readySandboxes does not place the new sandboxes\n // on this.readiedSandboxes. Hence the new sandboxes are private and nobody else can see them.\n debugging('supervisor') && console.log(`matchSlicesWithSandboxes: creating ${newCounter} new sandboxes, # of sandboxes ${this.sandboxes.length}`);\n const readied = await this.readySandboxes(newCounter, true /* allocateLocalSandboxes*/);\n // Track.\n trackReadiedSandboxes.push(...readied);\n\n for (const sandbox of readied) {\n assert(slicesThatNeedSandboxes.length > 0);\n // Pair\n const slice = slicesThatNeedSandboxes.pop();\n pair(sandbox, slice, sandboxKind.new);\n }\n \n // Put back any extras. There should not be any unless readySandboxes returned less than asked for.\n if (slicesThatNeedSandboxes.length > 0) {\n slicesThatNeedSandboxes.forEach(slice => {\n slice.allocated = false;\n this.queuedSlices.push(slice);\n });\n }\n }\n\n if ( false || debugging()) {\n console.log(`matchSlicesWithSandboxes: Matches: ${ this.dumpSandboxSlices(sandboxSlices) }`);\n this.dumpSandboxSlicesIfNotUnique(sandboxSlices, 'Warning: sandboxSlices; { sandbox, slice } pairs are not unique!');\n }\n } catch (e) {\n // Clear allocations.\n slicesToMatch.forEach(slice => { slice.allocated = false; });\n trackAssignedSandboxes.forEach(sandbox => { sandbox.allocated = false; sandbox.slice = null; });\n trackReadiedSandboxes.forEach(sandbox => { sandbox.allocated = false; sandbox.slice = null; sandbox.jobAddress = null; });\n \n // Filter out redundancies -- there shouldn't be any...\n slicesToMatch = slicesToMatch.filter(slice => this.queuedSlices.indexOf(slice) === -1);\n trackAssignedSandboxes = trackAssignedSandboxes.filter(sb => this.assignedSandboxes.indexOf(sb) === -1);\n trackReadiedSandboxes = trackReadiedSandboxes.filter(sb => this.readiedSandboxes.indexOf(sb) === -1);\n\n // Sanity checks.\n slicesToMatch.forEach(slice => { checkSlice(slice) });\n trackAssignedSandboxes.forEach(sandbox => { checkSandbox(sandbox, true /* isAssigned*/); });\n trackReadiedSandboxes.forEach(sandbox => { checkSandbox(sandbox, false /* isAssigned*/); });\n\n // Restore arrays.\n this.queuedSlices.push(...slicesToMatch);\n this.assignedSandboxes.push(...trackAssignedSandboxes);\n this.readiedSandboxes.push(...trackReadiedSandboxes);\n \n console.error('Error in matchSlicesWithSandboxes: Attempting to recover slices and sandboxes.', e);\n return [];\n } finally {\n this.matching = false;\n }\n\n debugging('supervisor') && console.log(`matchSlicesWithSandboxes: allocated ${sandboxSlices.length} sandboxes, queuedSlices ${this.queuedSlices.length}, unallocatedSpace ${this.unallocatedSpace}, matching ${this.matching}, fetching ${this.isFetchingNewWork}, # of sandboxes: ${this.sandboxes.length}.`);\n\n return sandboxSlices;\n }\n\n disassociateSandboxAndSlice(sandbox, slice) {\n this.returnSandbox(sandbox);\n sandbox.slice = null;\n this.returnSlice(slice, 'EUNCAUGHT');\n }\n\n /**\n * This method will call this.startSandboxWork(sandbox, slice) for each element { sandbox, slice }\n * of the array returned by this.matchSlicesWithSandboxes(availableSandboxes) until all allocated sandboxes\n * are working. It is possible for a sandbox to interleave with calling distributeQueuedSlices and leave a sandbox\n * that is not working. Moreover, this.queuedSlices may be exhausted before all sandboxes are working.\n * @returns {Promise<void>}\n */\n async distributeQueuedSlices () {\n const numCores = this.unallocatedSpace;\n\n // If there's nothing there, or we're reentering, bail out.\n if (this.queuedSlices.length === 0 || numCores <= 0 || this.matching) {\n // Interesting and noisy.\n // debugging('supervisor') && console.log(`Supervisor.distributeQueuedSlices: Do not nest work, fetch or matching slices with sandboxes: queuedSlices ${this.queuedSlices.length}, matching ${this.matching}, fetching ${this.isFetchingNewWork}, numCores ${numCores}`);\n return Promise.resolve();\n }\n\n //\n // Use the pseudo-mutex to prevent uncontrolled interleaving with fetchTask,\n // matchSlicesWithSandboxes and distributeQueuedSlices\n let sandboxSlices;\n this.acquire(numCores);\n try {\n sandboxSlices = await this.matchSlicesWithSandboxes(numCores);\n } finally {\n this.release();\n }\n\n debugging('supervisor') && console.log(`distributeQueuedSlices: ${sandboxSlices.length} sandboxSlices ${this.dumpSandboxSlices(sandboxSlices)}, matching ${this.matching}, fetching ${this.isFetchingNewWork}`);\n\n for (let sandboxSlice of sandboxSlices) {\n\n const { sandbox, slice } = sandboxSlice;\n try {\n if (sandbox.isReadyForAssign) {\n try {\n let timeoutMs = Math.floor(Math.min(+Supervisor.lastAssignFailTimerMs || 0, 10 * 60 * 1000 /* 10m */));\n await a$sleepMs(timeoutMs);\n await this.assignJobToSandbox(sandbox, slice.jobAddress);\n } catch (e) {\n console.error(`Supervisor.distributeQueuedSlices: Could not assign slice ${slice.identifier} to sandbox ${sandbox.identifier}.`);\n if (Supervisor.debugBuild) console.error(`...exception`, e);\n Supervisor.lastAssignFailTimerMs = Supervisor.lastAssignFailTimerMs ? +Supervisor.lastAssignFailTimerMs * 1.25 : Math.random() * 200;\n this.disassociateSandboxAndSlice(sandbox, slice);\n continue;\n }\n }\n\n if (!Supervisor.lastAssignFailTimerMs)\n Supervisor.lastAssignFailTimerMs = Math.random() * 200;\n this.startSandboxWork(sandbox, slice);\n Supervisor.lastAssignFailTimerMs = false;\n\n } catch (e) {\n // We should never get here.\n console.error(`Supervisor.distributeQueuedSlices: Failed to execute slice ${slice.identifier} in sandbox ${sandbox.identifier}.`);\n if (Supervisor.debugBuild) console.error('...exception', e);\n this.disassociateSandboxAndSlice(sandbox, slice);\n }\n }\n }\n\n /**\n *\n * @param {Sandbox} sandbox\n * @param {opaqueId} jobAddress\n * @returns {Promise<void>}\n */\n assignJobToSandbox(sandbox, jobAddress) {\n // Any error will be caught in distributeQueuedSlices.\n return sandbox.assign(jobAddress);\n }\n\n /**\n * Handles reassigning or returning a slice that was rejected by a sandbox.\n * \n * The sandbox will be terminated by this.returnSandbox in finalizeSandboxAndSlice. In this case,\n * if the slice does not have a rejected property already, reassign the slice to a new sandbox\n * and add a rejected property to the slice to indicate it has already rejected once, then set slice = null\n * in the return SandboxSlice so that finalizeSandboxAndSlice won't return slice to scheduler.\n * \n * If the slice rejects with a reason, or has a rejected time stamp (ie. has been rejected once already)\n * then return the slice and all slices from the job to the scheduler and\n * terminate all sandboxes with that jobAddress.\n * @param {Sandbox} sandbox \n * @param {Slice} slice\n * @returns {Promise<SandboxSlice>}\n */\n async handleWorkReject(sandbox, slice, rejectReason) {\n if (!this.rejectedJobReasons[slice.jobAddress])\n this.rejectedJobReasons[slice.jobAddress] = [];\n\n this.rejectedJobReasons[slice.jobAddress].push(rejectReason); // memoize reasons\n\n // First time rejecting without a reason. Try assigning slice to a new sandbox.\n if (rejectReason === 'false' && !slice.rejected) {\n // Set rejected.\n slice.rejected = Date.now();\n // Schedule the slice for execution.\n this.scheduleSlice(slice, true /* placeInTheFrontOfTheQueue*/, false /* noDuplicateExecution*/);\n \n // Null out slice so this.returnSlice will not be called in finalizeSandboxAndSlice.\n // But we still want this.returnSandbox to terminate the sandbox.\n slice = null;\n } else { // Slice has a reason OR rejected without a reason already and got stamped.\n \n // Purge all slices and sandboxes associated with slice.jobAddress .\n this.purgeAllWork(slice.jobAddress);\n // Clear jobAddress from this.cache .\n this.cleanJobCache();\n\n // Add to array of rejected jobs.\n let rejectedJob = {\n address: slice.jobAddress,\n reasons: this.rejectedJobReasons[slice.jobAddress],\n }\n this.rejectedJobs.push(rejectedJob);\n\n // Tell everyone all about it, when allowed.\n if (dcpConfig.worker.allowConsoleAccess || Supervisor.debugBuild)\n {\n if (slice.rejected)\n console.warn(`Supervisor.handleWorkReject: The slice ${slice.identifier} was rejected twice.`);\n else\n console.warn(`Supervisor.handleWorkReject: The slice ${slice.identifier} was rejected with reason ${rejectReason}.`);\n console.warn(' All slices with the same jobAddress returned to the scheduler.');\n console.warn(' All sandboxes with the same jobAddress are terminated.');\n }\n //\n // this.purgeAllWork(jobAddress) terminates all sandboxes with jobAddress,\n // and it also returns to scheduler all slices with jobAddress.\n // Therefore null out slice and sandbox so finalizeSandboxAndSlice doesn't do anything.\n // \n sandbox = null;\n slice = null;\n }\n return { sandbox, slice };\n }\n\n /**\n * Schedule the slice to be executed.\n * If slice is already executing and noDuplicateExecution is true, return the slice with reason.\n * @param {Slice} slice\n * @param {boolean} [placeInTheFrontOfTheQueue=false]\n * @param {boolean} [noDuplicateExecution=true]\n * @param {string} [reason]\n */\n scheduleSlice(slice, placeInTheFrontOfTheQueue = false, noDuplicateExecution = true, reason) {\n // When noDuplicateExecution, if slice is already executing, do nothing.\n let workingSlices = [];\n if (noDuplicateExecution)\n workingSlices = this.allocatedSlices;\n\n if (!workingSlices.indexOf(slice)) {\n // Reset slice state to allow execution.\n slice.status = SLICE_STATUS_UNASSIGNED;\n slice.allocated = false;\n // Enqueue in the to-be-executed queue.\n if (placeInTheFrontOfTheQueue) this.queuedSlices.unshift(slice);\n else this.queuedSlices.push(slice);\n }\n }\n\n /**\n * Purge all slices and sandboxes with this jobAddress.\n * @param {address} jobAddress\n * @param {boolean} [onlyPurgeQueuedAndAllocated=false]\n */\n purgeAllWork(jobAddress, onlyPurgeQueuedAndAllocated = false) {\n // Purge all slices and sandboxes associated with jobAddress .\n const deadSandboxes = this.sandboxes.filter(sandbox => sandbox.jobAddress === jobAddress);\n\n if (deadSandboxes.length > 0) {\n debugging('supervisor') && console.log(`purgeAllWork(${this.dumpJobAddress(jobAddress)}): sandboxes purged ${deadSandboxes.map(s => s.id)}, # of sandboxes ${this.sandboxes.length}`);\n deadSandboxes.forEach(sandbox => this.returnSandbox(sandbox));\n }\n\n let deadSlices;\n if (onlyPurgeQueuedAndAllocated) {\n deadSlices = this.queuedSlices.filter(slice => slice.jobAddress === jobAddress);\n if (deadSlices.length > 0 || this.allocatedSlices.length > 0)\n debugging('supervisor') && console.log(`purgeAllWork(${this.dumpJobAddress(jobAddress)}): dead queuedSlices ${deadSlices.map(s => s.sliceNumber)}, dead allocatedSlices ${this.allocatedSlices.map(s => s.sliceNumber)}`);\n deadSlices.push(...this.allocatedSlices);\n } else {\n deadSlices = this.slices.filter(slice => slice.jobAddress === jobAddress);\n }\n\n if (deadSlices.length > 0) {\n debugging('supervisor') && console.log(`purgeAllWork(${this.dumpJobAddress(jobAddress)}): slices purged ${deadSlices.map(s => s.sliceNumber)}, # of sandboxes ${this.sandboxes.length}`);\n this.returnSlices(deadSlices);\n this.removeQueuedSlices(deadSlices);\n }\n debugging('supervisor') && console.log(`purgeAllWork(${this.dumpJobAddress(jobAddress)}): Finished: slices ${this.slices.length}, queuedSlices ${this.queuedSlices.length}, assigned ${this.assignedSandboxes.length}, readied ${this.readiedSandboxes.length}, # of sandboxes ${this.sandboxes.length}`);\n }\n\n /**\n * Gives a slice to a sandbox which begins working. Handles collecting\n * the slice result (complete/fail) from the sandbox and submitting the result to the scheduler.\n * It will also return the sandbox to @this.returnSandbox when completed so the sandbox can be re-assigned.\n *\n * @param {Sandbox} sandbox - the sandbox to give the slice\n * @param {Slice} slice - the slice to distribute\n * @returns {Promise<void>} Promise returned from sandbox.run\n */\n async startSandboxWork (sandbox, slice) {\n var startDelayMs, reason = 'unknown';\n\n try {\n slice.markAsWorking();\n } catch (e) {\n // This will occur when the same slice is distributed twice.\n // It is normal because two sandboxes could finish at the same time and be assigned the\n // same slice before the slice is marked as working.\n debugging() && console.debug('startSandboxWork: slice.markAsWorking exception:', e);\n return Promise.resolve();\n }\n\n // sandbox.requiresGPU = slice.requiresGPU;\n // if (sandbox.requiresGPU) {\n // this.GPUsAssigned++;\n // }\n\n if (Supervisor.startSandboxWork_beenCalled)\n startDelayMs = 1000 * (tuning.minSandboxStartDelay + (Math.random() * (tuning.maxSandboxStartDelay - tuning.minSandboxStartDelay)));\n else {\n startDelayMs = 1000 * tuning.minSandboxStartDelay;\n Supervisor.startSandboxWork_beenCalled = true;\n }\n\n try {\n debugging() && console.log(`startSandboxWork: Started ${this.dumpStatefulSandboxAndSlice(sandbox, slice)}, total sandbox count: ${this.sandboxes.length}, matching ${this.matching}, fetching ${this.isFetchingNewWork}`);\n if (Supervisor.sliceTiming) {\n slice['pairingDelta'] = Date.now() - slice['pairingDelta'];\n slice['executionDelta'] = Date.now();\n }\n let result;\n try {\n result = await sandbox.work(slice, startDelayMs);\n } finally {\n sandbox.allocated = false;\n slice.allocated = false;\n }\n if (Supervisor.sliceTiming) {\n slice['executionDelta'] = Date.now() - slice['executionDelta'];\n slice['resultDelta'] = Date.now();\n }\n slice.collectResult(result, true);\n // In watchdog, all sandboxes in working state, have their slice status sent to result submitter.\n // However, this can happen after the sandbox/slice has already sent results\n // to result submitter, in which case, the activeSlices table has already removed the row\n // corresponding to slice and hence is incapable of updating status.\n sandbox.changeWorkingToAssigned();\n this.assignedSandboxes.push(sandbox);\n debugging() && console.log(`startSandboxWork: Finished ${this.dumpStatefulSandboxAndSlice(sandbox, slice)}, total sandbox count: ${this.sandboxes.length}, matching ${this.matching}, fetching ${this.isFetchingNewWork}`);\n } catch(error) {\n let logLevel;\n\n if (error instanceof SandboxError) {\n logLevel = 'warn';\n // The message and stack properties of error objects are not enumerable,\n // so they have to be copied into a plain object this way\n const errorResult = Object.getOwnPropertyNames(error).reduce((o, p) => {\n o[p] = error[p]; return o;\n }, { message: 'Unexpected worker error' });\n slice.collectResult(errorResult, false);\n } else {\n logLevel = 'error';\n // This error was unrelated to the work being done, so just return the slice in the finally block.\n // For extra safety the sandbox is terminated.\n slice.result = null;\n slice.status = SLICE_STATUS_FAILED; /** XXXpfr @todo terminating sandbox? */\n }\n\n let errorString;\n switch (error.errorCode) {\n case 'ENOPROGRESS':\n reason = 'ENOPROGRESS';\n errorString = 'Supervisor.startSandboxWork - No progress error in sandbox.\\n';\n break;\n case 'ESLICETOOSLOW':\n reason = 'ESLICETOOSLOW';\n errorString = 'Supervisor.startSandboxWork - Slice too slow error in sandbox.\\n';\n break;\n case 'EUNCAUGHT':\n reason = 'EUNCAUGHT';\n errorString = `Supervisor.startSandboxWork - Uncaught error in sandbox ${error.message}.\\n`;\n break;\n case 'EFETCH':\n // reason = 'EFETCH'; The status.js processing cannot handle 'EFETCH'\n reason = 'unknown';\n errorString = `Supervisor.startSandboxWork - Could not fetch data: ${error.message}.\\n`;\n break;\n }\n \n const { getenv } = __webpack_require__(/*! ../../common/dcp-env */ \"./src/common/dcp-env.js\");\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 let workerConsole = sandbox.supervisorCache.cache.job[slice.jobAddress].workerConsole;\n const displayMaxInfo = Boolean(getenv('DCP_SUPERVISOR_DEBUG_DISPLAY_MAX_INFO')) || (workerConsole && dcpConfig.worker.allowConsoleAccess);\n\n const errorObject = {\n jobAddress: slice.jobAddress.substr(0,10),\n sliceNumber: slice.sliceNumber,\n sandbox: sandbox.id,\n jobName: sandbox.public ? sandbox.public.name : 'unnamed',\n };\n \n if (error.name === 'EWORKREJECT') {\n error.stack = 'Sandbox was terminated by work.reject()';\n const ss = await this.handleWorkReject(sandbox, slice, error.message);\n sandbox = ss.sandbox; slice = ss.slice;\n }\n\n if (!displayMaxInfo && error.errorCode === 'EUNCAUGHTERROR') {\n console[logLevel](`Supervisor.startSandboxWork - Uncaught error in sandbox, could not compute.\\n`, errorObject);\n } else if (!displayMaxInfo && error.errorCode === 'EFETCH_BAD_ORIGIN') {\n console[logLevel](`Supervisor.startSandboxWork - Could not fetch data: ${error.message}`);\n } else if (!displayMaxInfo && errorString) {\n console[logLevel](errorString, errorObject);\n } else if (!displayMaxInfo && error.name === 'EWORKREJECT') {\n console[logLevel](`Supervisor.startSandboxWork - Sandbox rejected work: ${error.message}`)\n } else {\n if (displayMaxInfo)\n errorObject.stack += '\\n --------------------\\n' + (error.stack.split('\\n').slice(1).join('\\n'));\n console[logLevel](`Supervisor.startSandboxWork - Sandbox failed: ${error.message}\\n`, errorObject);\n }\n } finally {\n await this.finalizeSandboxAndSlice(sandbox, slice, reason);\n }\n }\n\n /**\n * If slice && slice.result, then call await this.recordResult(slice) and this.returnSandbox(sandbox, slice) will have no effect.\n * If slice && !slice.result, then call this.returnSlice(slice, reason) and then this.returnSandbox(sandbox, slice) which terminates sandbox.\n * If !slice && sandbox, then terminate the sandbox with this.returnSandbox(sandbox, slice) .\n * If !slice && !sandbox, then do nothing.\n * @param {Sandbox} [sandbox]\n * @param {Slice} [slice]\n * @param {string} [reason]\n */\n async finalizeSandboxAndSlice(sandbox, slice, reason) {\n debugging('supervisor') && console.log(`finalizeSandboxAndSlice: sandbox ${sandbox ? sandbox.identifier : 'nade'}, slice ${slice ? slice.identifier : 'nade'}`);\n if (slice) {\n if (slice.result) await this.recordResult(slice);\n else this.returnSlice(slice, reason);\n }\n // It is possible that sandbox is already terminated\n // Because sandbox.allocated=false as soon as sandbox.work(...) completes.\n // But the await at or in finalizeSandboxAndSlice may allow pruneSandboxes to slither in.\n if (sandbox) this.returnSandbox(sandbox, slice, false /* verifySandboxIsNotTerminated*/);\n }\n\n /**\n * Terminates sandboxes and returns slices.\n * Sets the working flag to false, call @this.work to start working again.\n * \n * If forceTerminate is true: Terminates all sandboxes and returns all slices.\n * If forceTerminate is false: Terminates non-allocated sandboxes and returns queued slices.\n *\n * @param {boolean} [forceTerminate = true] - true if you want to stop the sandboxes from completing their current slice.\n * @returns {Promise<void>}\n */\n async stopWork (forceTerminate = true) {\n debugging('supervisor') && console.log('stopWork(${forceTerminate}): terminating sandboxes and returning slices to scheduler.');\n if (forceTerminate) {\n while (this.sandboxes.length) {\n this.returnSandbox(this.sandboxes[0], null, false);\n }\n\n await this.returnSlices(this.slices).then(() => {\n this.queuedSlices.length = 0;\n });\n } else {\n // Only terminate idle sandboxes and return only queued slices\n let idleSandboxes = this.sandboxes.filter(w => !w.allocated);\n for (const sandbox of idleSandboxes) {\n this.returnSandbox(sandbox, null, false /* verifySandboxIsNotTerminated*/);\n }\n\n await this.returnSlices(this.queuedSlices).then(() => {\n this.queuedSlices.length = 0;\n });\n\n await new Promise((resolve, reject) => {\n let sandboxesRemaining = this.allocatedSandboxes.length;\n if (sandboxesRemaining === 0)\n {\n resolve();\n }\n // Resolve and finish work once all sandboxes have finished submitting their results.\n this.on('submitFinished', () => {\n sandboxesRemaining--;\n if (sandboxesRemaining === 0)\n {\n console.log('All sandboxes empty, stopping worker and closing all connections');\n resolve();\n }\n });\n });\n }\n\n if (this.resultSubmitterConnection) {\n this.resultSubmitterConnection.off('close', this.openResultSubmitterConn);\n this.resultSubmitterConnection.close();\n this.resultSubmitterConnection = null;\n }\n\n if (this.taskDistributorConnection) {\n this.taskDistributorConnection.off('close', this.openTaskDistributorConn);\n this.taskDistributorConnection.close();\n this.taskDistributorConnection = null;\n }\n\n if (this.packageManagerConnection) {\n this.packageManagerConnection.off('close', this.openPackageManagerConn);\n this.packageManagerConnection.close();\n this.packageManagerConnection = null;\n }\n\n if (this.eventRouterConnection) {\n this.eventRouterConnection.off('close', this.openEventRouterConn);\n this.eventRouterConnection.close();\n this.eventRouterConnection = null;\n }\n\n this.emit('stop');\n }\n\n /**\n * Takes a slice and returns it to the scheduler to be redistributed.\n * Usually called when an exception is thrown by sandbox.work(slice, startDelayMs) .\n * Or when the supervisor tells it to forcibly stop working.\n *\n * @param {Slice} slice - The slice to return to the scheduler.\n * @param {string} [reason] - Optional reason for the return: 'ENOPROGRESS', 'EUNCAUGHT', 'ESLICETOOSLOW', 'unknown'.\n * @returns {Promise<*>} - Response from the scheduler.\n */\n returnSlice (slice, reason) {\n // When sliceNumber === 0 don't send a status message.\n if (slice.sliceNumber === 0) return Promise.resolve();\n \n debugging() && console.log(`Supervisor.returnSlice: Returning slice ${slice.identifier} with reason ${reason}.`);\n \n const payload = slice.getReturnMessagePayload(this.workerOpaqueId, reason);\n try\n {\n return this.resultSubmitterConnection.send('status', payload) /* resultSubmitterConnection can be null if worker is stopped */\n .then(response => {\n return response;\n }).catch(error => {\n debugging('supervisor') && console.error('Failed to return slice', {\n sliceNumber: slice.sliceNumber,\n jobAddress: slice.jobAddress,\n status: slice.status,\n error,\n }, 'Will try again on a new connection.');\n this.resultSubmitterMessageQueue.push({ operation: 'status', data: payload });\n this.resultSubmitterConnection.close();\n });\n }\n catch (error)\n {\n debugging('supervisor') && console.error(`Failed to return slice ${slice.identifier}, no connection to result submitter:`, error);\n }\n }\n\n /**\n * Bulk-return multiple slices, possibly for assorted jobs.\n * Returns slices to the scheduler to be redistributed.\n * Called in the sandbox terminate handler and purgeAllWork(jobAddress)\n * and stopWork(forceTerminate).\n *\n * @param {Slice[]} slices - The slices to return to the scheduler.\n * @returns {Promise<void>} - Response from the scheduler.\n */\n async returnSlices(slices) {\n if (!slices || !slices.length) return Promise.resolve();\n \n const slicePayload = [];\n slices.forEach(slice => { addToReturnSlicePayload(slicePayload, slice); });\n this.removeSlices(slices);\n\n debugging('supervisor') && console.log(`Supervisor.returnSlices: Returning slices ${this.dumpSlices(slices)}.`);\n\n return this.resultSubmitterConnection.send('status', {\n worker: this.workerOpaqueId,\n slices: slicePayload,\n }).then(response => {\n return response;\n }).catch(error => {\n const errorInfo = slices.map(slice => slice.identifier);\n debugging('supervisor') && console.error('Failed to return slice(s)', { errorInfo, error }, 'Will try again on new connection.');\n this.resultSubmitterMessageQueue.push({ operation: 'status', data: { worker: this.workerOpaqueId, slices: slicePayload } })\n this.resultSubmitterConnection.close();\n // Just in case the caller is expecing a DCP response\n return { success: false, payload: {} };\n });\n }\n\n /**\n * Submits the slice results to the scheduler, either to the\n * work submit or fail endpoints based on the slice status.\n * Then remove the slice from the @this.slices cache.\n *\n * @param {Slice} slice - The slice to submit.\n * @returns {Promise<void>}\n */\n async recordResult (slice) {\n // It is possible for slice.result to be undefined when there are upstream errors.\n if ( !(slice && slice.result))\n throw new Error(`recordResult: slice.result is undefined for slice ${slice.identifier}. This is ok when there are upstream errors.`);\n\n debugging('supervisor') && console.log(`supervisor: recording result for slice ${slice.identifier}.`);\n\n const jobAddress = slice.jobAddress;\n const sliceNumber = slice.sliceNumber;\n const authorizationMessage = slice.getAuthorizationMessage();\n\n /* @see result-submitter::result for full message details */\n const metrics = { GPUTime: 0, CPUTime: 0, CPUDensity: 0, GPUDensity: 0, total: 0 };\n const payloadData = {\n slice: sliceNumber,\n job: jobAddress,\n worker: this.workerOpaqueId,\n paymentAddress: this.paymentAddress,\n metrics,\n authorizationMessage,\n }\n\n const timeReport = slice.timeReport;\n if (timeReport && timeReport.total > 0) {\n metrics.GPUTime = timeReport.webGL;\n metrics.CPUTime = timeReport.CPU;\n metrics.CPUDensity = metrics.CPUTime / timeReport.total;\n metrics.GPUDensity = metrics.GPUTime / timeReport.total;\n metrics.total = timeReport.total;\n metrics.CPUTime = 1 + Math.floor(metrics.CPUTime);\n if (metrics.GPUTime > 0)\n metrics.GPUTime = 1 + Math.floor(metrics.GPUTime);\n }\n \n this.emit('submittingResult');\n\n if (!slice.isFinished)\n throw new Error('Cannot record result for slice that is not finished');\n\n if (slice.resultStorageType === 'pattern') { /* This is a remote-storage slice. */\n const remoteResult = await this.sendResultToRemote(slice);\n payloadData.result = encodeDataURI(JSON.stringify(remoteResult));\n } else {\n payloadData.result = encodeDataURI(slice.result.result); /* XXXwg - result.result is awful */\n }\n debugging('supervisor') && console.log('Supervisor.recordResult: payloadData.result', payloadData.result.slice(0, 512));\n\n try {\n if (slice.completed) {\n\n /* work function returned a result */\n let resp = await this.resultSubmitterConnection.send(\n 'result',\n payloadData,\n )\n \n if (!resp.success) {\n if (resp.payload && resp.payload.code === 'DCPS-01002') { /* result submitter cannot connect to event router; try again */\n try {\n resp = await this.resendResult(payloadData)\n } catch (error) {\n debugging('supervisor') && console.error(`supervisor - failed to submit result for job ${jobAddress} after ${payloadData.sendRetries} attempts`)\n throw error;\n }\n }\n else\n throw new Error(`failed to submit result for slice ${slice.sliceNumber} of job ${jobAddress}`);\n }\n\n if (false) {}\n\n const receipt = {\n accepted: true,\n payment: resp.payload.slicePaymentAmount,\n };\n this.emit('submittedResult', resp.payload);\n this.emit('dccCredit', receipt);\n } else {\n /* slice did not complete for some reason */\n \n // If the slice from a job never completes and the job address exists in the ringBufferofJobs, \n // then we remove it to allow for another slice (from the same job) to be obtained by fetchTask\n this.ringBufferofJobs.buf = this.ringBufferofJobs.filter(element => element !== jobAddress);\n \n await this.returnSlice(slice);\n }\n } catch(error) {\n console.info(`1014: Failed to submit results for slice ${payloadData.slice} of job ${payloadData.job}`, error);\n this.emit('submitSliceFailed', error);\n } finally {\n this.emit('submitFinished');\n // Remove the slice from the slices array.\n this.removeSlice(slice);\n if (Supervisor.sliceTiming) {\n slice['resultDelta'] = Date.now() - slice['resultDelta'];\n console.log(`recordResult(${slice['pairingDelta']}, ${slice['executionDelta']}, ${slice['resultDelta']}): Completed slice ${slice.identifier}.`);\n } else\n debugging('supervisor') && console.log(`recordResult: Completed slice ${slice.identifier}.`);\n }\n }\n\n /**\n * Send a work function's result to a server that speaks our DCP Remote Data Server protocol.\n * The data server dcp-rds is been implemented in https://gitlab.com/Distributed-Compute-Protocol/dcp-rds .\n *\n * @param {Slice} slice - Slice object whose result we are sending.\n * @returns {Promise<object>} - Object of the form { success: true, href: 'http://127.0.0.1:3521/methods/download/jobs/34/result/10' } .\n * @throws When HTTP status not in the 2xx range.\n */\n async sendResultToRemote(slice) {\n const postParams = {\n ...slice.resultStorageParams\n };\n \n const patternParams = {\n slice: slice.sliceNUmber,\n job: slice.jobAddress\n };\n \n /**\n * @todo Future Work: we need to pass the pattern parameters with the job details into the supervisor. \n * We do not have all the information (necessarily) to calculate them from here. /wg jan 2022\n */\n const sliceResultUri = makeValueURI('pattern', slice.resultStorageDetails, patternParams)\n\n debugging() && console.log('sendResultToRemote sliceResultUri: ', sliceResultUri);\n \n if (this.makeSafeOriginList('sendResults').indexOf(sliceResultUri.origin) === -1)\n throw new Error(`Invalid origin for remote result storage: '${sliceResultUri.origin}'`);\n\n postParams.element = slice.sliceNumber;\n postParams.contentType = 'application/json'; // Currently data will be outputed as a JSON object, @todo: Support file upload.\n\n debugging() && console.log('sendResultToRemote: postParams: ', postParams);\n\n let result = slice.result.result;\n if (result) {\n postParams.content = JSON.stringify(result);\n } else {\n postParams.error = JSON.stringify(slice.error);\n }\n\n debugging('supervisor') && console.log('sendResultToRemote: content: ', (result ? postParams.content : postParams.error).slice(0, 512));\n\n //\n // Notes:\n // 1) In recordResults the response from justFetch is JSON serialized and encodeDataURI is called.\n // payloadData.result = await this.sendResultToRemote(slice);\n // payloadData.result = encodeDataURI(JSON.stringify(payloadData.result));\n // 2) We do further processing after the call to sendResultToRemote in recordResult, because\n // if we did it here there would be a perf hit. When the return value is a promise, it gets\n // folded into sendResultToRemote's main promise. If justFetch's promise wasn't a return value then\n // justFetch would be separately added to the micro-task-queue.\n return await justFetch(sliceResultUri, 'JSON', 'POST', false, postParams);\n }\n}\n\n/**\n * Sandbox has had an error which is not from the work function: kill it\n * and try to redo the slice.\n */\nfunction handleSandboxError(supervisor, sandbox, error) {\n const slice = sandbox.slice;\n\n slice.sandboxErrorCount = (slice.sandboxErrorCount || 0) + 1;\n sandbox.slice = null;\n supervisor.returnSandbox(sandbox); /* terminate the sandbox */\n slice.status = SLICE_STATUS_UNASSIGNED; /* ToT */\n slice.allocated = false;\n console.warn(`Supervisor.handleSandboxError: Sandbox ${sandbox.identifier}...(${sandbox.public.name}/${slice.sandboxErrorCount}) with slice ${slice.identifier} had error.`, error);\n\n if (slice.sandboxErrorCount < dcpConfig.worker.maxSandboxErrorsPerSlice)\n supervisor.queuedSlices.push(slice);\n else {\n slice.error = error;\n supervisor.returnSlice(slice);\n }\n}\n\n/**\n * Add a slice to the slice payload being built. If a sliceList already exists for the\n * job-status-authMessage tuple, then the slice will be added to that, otherwise a new\n * sliceList will be added to the payload.\n *\n * @param {Object[]} slicePayload - Slice payload being built. Will be mutated in place.\n * @param {Slice} slice - The slice.\n * @param {String} status - Status update, eg. progress or scheduled.\n *\n * @returns {Object[]} mutated slicePayload array\n */\nfunction addToSlicePayload(slicePayload, slice, status) {\n // getAuthorizationMessage helps enforces the equivalence\n // !authorizationMessage <==> sliceNumber === 0\n const authorizationMessage = slice.getAuthorizationMessage();\n if (!authorizationMessage) return;\n\n // Try to find a sliceList in the payload which matches the job, status, and auth message\n let sliceList = slicePayload.find(desc => {\n return desc.job === slice.jobAddress\n && desc.status === status\n && desc.authorizationMessage === authorizationMessage;\n });\n\n // If we didn't find a sliceList, start a new one and add it to the payload\n if (!sliceList) {\n sliceList = {\n job: slice.jobAddress,\n sliceNumbers: [],\n status,\n authorizationMessage,\n };\n slicePayload.push(sliceList);\n }\n\n sliceList.sliceNumbers.push(slice.sliceNumber);\n\n return slicePayload;\n}\n\n/**\n * Add a slice to the returnSlice payload being built. If a sliceList already exists for the\n * job-isEstimation-authMessage-reason tuple, then the slice will be added to that, otherwise a new\n * sliceList will be added to the payload.\n *\n * @param {Object[]} slicePayload - Slice payload being built. Will be mutated in place.\n * @param {Slice} slice - The slice.\n * @param {String} [reason] - Optional reason to further characterize status; e.g. 'ENOPROGRESS', 'EUNCAUGHT', 'ESLICETOOSLOW', 'unknown'.\n *\n * @returns {Object[]} mutated slicePayload array\n */\nfunction addToReturnSlicePayload(slicePayload, slice, reason) {\n // getAuthorizationMessage helps enforces the equivalence\n // !authorizationMessage <==> sliceNumber === 0\n const authorizationMessage = slice.getAuthorizationMessage();\n if (!authorizationMessage) return;\n\n if (!reason) reason = slice.error ? 'EUNCAUGHT' : 'unknown';\n\n // Try to find a sliceList in the payload which matches the job, status, and auth message\n let sliceList = slicePayload.find(desc => {\n return desc.job === slice.jobAddress\n && desc.isEstimationSlice === slice.isEstimationSlice\n && desc.authorizationMessage === authorizationMessage\n && desc.reason === reason;\n });\n\n // If we didn't find a sliceList, start a new one and add it to the payload\n if (!sliceList) {\n sliceList = {\n job: slice.jobAddress,\n sliceNumbers: [],\n status: 'return',\n isEstimationSlice: slice.isEstimationSlice,\n authorizationMessage,\n reason,\n };\n slicePayload.push(sliceList);\n }\n\n sliceList.sliceNumbers.push(slice.sliceNumber);\n\n return slicePayload;\n}\n\n/**\n * Return DCPv4-specific connection options, composed of type-specific, URL-specific, \n * and worker-specific options, any/all of which can override the dcpConfig.dcp.connectOptions.\n * The order of precedence is the order of specificity.\n */\nfunction connectionOptions(url, label) {\n return leafMerge(/* ordered from most to least specific */\n dcpConfig.worker.dcp.connectionOptions.default,\n dcpConfig.worker.dcp.connectionOptions[label],\n dcpConfig.worker.dcp.connectionOptions[url.href]);\n}\n\n/** @type {number | boolean} */\nSupervisor.lastAssignFailTimerMs = false;\n/** @type {boolean} */\nSupervisor.startSandboxWork_beenCalled = false;\n/** @type {boolean} */\nSupervisor.debugBuild = ((__webpack_require__(/*! dcp/common/dcp-build */ \"./src/common/dcp-build.js\").build) === 'debug');\n/**\n * When Supervisor.sliceTiming is set to be true, it displays the timings of a every slice\n * slice['pairingDelta'] = timespan of when slice is paired with sandbox until execution starts\n * slice['executionDelta'] = timespan of execution in sandbox\n * slice['resultDelta'] = timespan of when sandbox finishes executing until recordResult completes.\n * @type {boolean}\n */\nSupervisor.sliceTiming = false;\n\nexports.Supervisor = Supervisor;\n\n\n//# sourceURL=webpack://dcp/./src/dcp-client/worker/supervisor.js?");
|
|
4517
|
+
eval("/* provided dependency */ var process = __webpack_require__(/*! ./node_modules/process/browser.js */ \"./node_modules/process/browser.js\");\n/**\n * @file worker/supervisor.js\n *\n * The component that controls each of the sandboxes\n * and distributes work to them. Also communicates with the\n * scheduler to fetch said work.\n *\n * The supervisor readies sandboxes before/while fetching slices.\n * This means sometimes there are extra instantiated WebWorkers\n * that are idle (in this.readiedSandboxes). Readied sandboxes can\n * be used for any slice. After a readied sandbox is given a slice\n * it becomes assigned to slice's job and can only do work\n * for that job.\n *\n * After a sandbox completes its work, the sandbox becomes cached\n * and can be reused if another slice with a matching job is fetched.\n *\n * @author Matthew Palma, mpalma@kingsds.network\n * Ryan Rossiter, ryan@kingsds.network\n * @date May 2019\n */\n\n/* global dcpConfig */\n// @ts-check\n\n\nconst constants = __webpack_require__(/*! dcp/common/scheduler-constants */ \"./src/common/scheduler-constants.js\");\nconst hash = __webpack_require__(/*! dcp/common/hash */ \"./src/common/hash.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 DCP_ENV = __webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\");\n\nconst debugging = (__webpack_require__(/*! dcp/debugging */ \"./src/debugging.js\").scope)('worker');\nconst { assert } = __webpack_require__(/*! dcp/common/dcp-assert */ \"./src/common/dcp-assert.js\");\nconst { EventEmitter } = __webpack_require__(/*! dcp/common/dcp-events */ \"./src/common/dcp-events/index.js\");\nconst { Sandbox, SandboxError } = __webpack_require__(/*! ./sandbox */ \"./src/dcp-client/worker/sandbox.js\");\nconst { Slice, SLICE_STATUS_UNASSIGNED, SLICE_STATUS_FAILED } = __webpack_require__(/*! ./slice */ \"./src/dcp-client/worker/slice.js\");\nconst { SupervisorCache } = __webpack_require__(/*! ./supervisor-cache */ \"./src/dcp-client/worker/supervisor-cache.js\");\nconst { DcpURL } = __webpack_require__(/*! dcp/common/dcp-url */ \"./src/common/dcp-url.js\");\nconst { requireNative } = __webpack_require__(/*! dcp/dcp-client/webpack-native-bridge */ \"./src/dcp-client/webpack-native-bridge.js\");\nconst { localStorage } = __webpack_require__(/*! dcp/common/dcp-localstorage */ \"./src/common/dcp-localstorage.js\");\nconst { booley, encodeDataURI, makeValueURI, leafMerge, a$sleepMs, justFetch, compressJobMap, toJobMap,\n compressSandboxes, compressSlices, truncateAddress, dumpSandboxesIfNotUnique, dumpSlicesIfNotUnique, \n generateOpaqueId } = __webpack_require__(/*! dcp/utils */ \"./src/utils/index.js\");\nconst { DCPError } = __webpack_require__(/*! dcp/common/dcp-error */ \"./src/common/dcp-error.js\");\nconst { sliceStatus } = __webpack_require__(/*! dcp/common/scheduler-constants */ \"./src/common/scheduler-constants.js\");\nconst { calculateJoinHash } = __webpack_require__(/*! dcp/dcp-client/compute-groups */ \"./src/dcp-client/compute-groups/index.js\");\nconst RingBuffer = __webpack_require__(/*! dcp/utils/ringBuffer */ \"./src/utils/ringBuffer.js\");\nconst supervisorTuning = dcpConfig.future('worker.tuning');\nconst tuning = {\n watchdogInterval: 7, /**< seconds - time between fetches when ENOTASK(? /wg nov 2019) */\n minSandboxStartDelay: 0.1, /**< seconds - minimum time between WebWorker starts */\n maxSandboxStartDelay: 0.7, /**< seconds - maximum delay time between WebWorker starts */\n ...supervisorTuning\n};\n\n/** Make timers 10x slower when running in niim */\nlet timeDilation = 1;\nif (DCP_ENV.platform === 'nodejs') {\n /** Make timers 10x slower when running in niim */\n timeDilation = (requireNative('module')._cache.niim instanceof requireNative('module').Module) ? 10 : 1;\n}\n\ndcpConfig.future('worker.sandbox', { progressReportInterval: (5 * 60 * 1000) });\nconst sandboxTuning = dcpConfig.worker.sandbox;\n\n/**\n * @typedef {*} address\n * @typedef {*} opaqueId\n */\n\n/**\n * @typedef {object} SandboxSlice\n * @property {Sandbox} sandbox\n * @property {Slice} slice\n */\n\n/**\n * @typedef {object} Signature\n * @property {Uint8Array} r\n * @property {Uint8Array} s\n * @property {Uint8Array} v\n */\n\n/**\n * @typedef {object} SignedAuthorizationMessageObject\n * @property {object} auth\n * @property {Signature} signature\n * @property {module:dcp/wallet.Address} owner\n */\n\n/** @typedef {import('.').Worker} Worker */\n/** @typedef {import('.').SupervisorOptions} SupervisorOptions */\n\nclass Supervisor extends EventEmitter {\n /**\n * @constructor\n * @param {Worker} worker\n * @param {SupervisorOptions} options\n */\n constructor (worker, options={}) {\n super('Supervisor');\n\n /** @type {Worker} */\n this.worker = worker;\n\n /** @type {Sandbox[]} */\n this.sandboxes = [];\n\n /** @type {Sandbox[]} */\n this.readiedSandboxes = [];\n\n /** @type {Sandbox[]} */\n this.assignedSandboxes = [];\n\n /** @type {Slice[]} */\n this.slices = [];\n\n /** @type {Slice[]} */\n this.queuedSlices = [];\n\n /** @type {Slice[]} */\n this.lostSlices = [];\n\n /** @type {boolean} */\n this.matching = false;\n\n /** @type {boolean} */\n this.isFetchingNewWork = false;\n\n /** @type {number} */\n this.numberOfCoresReserved = 0;\n\n /** @type {number} */\n this.addressTruncationLength = 20; // Set to -1 for no truncation.\n\n /** @type {Object[]} */\n this.rejectedJobs = [];\n this.rejectedJobReasons = [];\n\n if (!options) {\n console.error('Supervisor Options', options, new Error().stack);\n options = {};\n }\n\n /** @type {object} */\n this.options = {\n jobAddresses: options.jobAddresses || [/* all jobs unless priorityOnly */],\n ...options,\n };\n\n const { paymentAddress, identity } = options;\n if (paymentAddress) {\n if (paymentAddress instanceof wallet.Keystore) {\n this.paymentAddress = paymentAddress.address;\n } else {\n this.paymentAddress = new wallet.Address(paymentAddress);\n }\n } else {\n this.paymentAddress = null;\n }\n\n this._identityKeystore = identity;\n\n this.extraAllowOrigins = {\n any: [],\n fetchData: [],\n fetchWorkFunctions: [],\n fetchArguments: [],\n sendResults: [],\n };\n \n // Backwards compatability with deprecated interface.\n if (typeof options.allowedOrigins !== 'undefined')\n {\n if (!Array.isArray(options.allowedOrigins))\n {\n for (let kind in this.extraAllowOrigins)\n {\n if (options.allowedOrigins[kind])\n this.extraAllowOrigins[kind].push(...options.allowedOrigins[kind]); \n }\n }\n else\n this.extraAllowOrigins['any'].push(...options.allowedOrigins)\n \n delete options.allowedOrigins;\n }\n /* Assume that an array of dcpConfig.worker.allowOrigins means they can be used for anything */\n if (Array.isArray(dcpConfig.worker.allowOrigins))\n dcpConfig.worker.allowOrigins = { any: dcpConfig.worker.allowOrigins };\n \n /**\n * Maximum sandboxes allowed to work at a given time.\n * @type {number}\n */\n this.maxWorkingSandboxes = options.maxWorkingSandboxes || 1;\n\n /** @type {number} */\n this.defaultMaxGPUs = 1;\n // this.GPUsAssigned = 0;\n \n // Object.defineProperty(this, 'GPUsAssigned', {\n // get: () => this.allocatedSandboxes.filter(sb => !!sb.requiresGPU).length,\n // enumerable: true,\n // configurable: false,\n // });\n\n /**\n * TODO: Remove this when the supervisor sends all of the sandbox\n * capabilities to the scheduler when fetching work.\n * @type {object}\n */\n this.capabilities = null;\n\n /** @type {number} */\n this.lastProgressReport = 0;\n\n /** \n * An N-slot ring buffer of job addresses. Stores all jobs that have had no more than 1 slice run in the ring buffer.\n * Required for the implementation of discrete jobs \n * @type {RingBuffer} \n */\n this.ringBufferofJobs = new RingBuffer(200); // N = 200 should be more than enough.\n \n // @hack - dcp-env.isBrowserPlatform is not set unless the platform is _explicitly_ set,\n // using the default detected platform doesn't set it.\n // Fixing that causes an error in the wallet module's startup on web platform, which I\n // probably can't fix in a reasonable time this morning.\n // ~ER2020-02-20\n\n if (!options.maxWorkingSandboxes\n && DCP_ENV.browserPlatformList.includes(DCP_ENV.platform)\n && navigator.hardwareConcurrency > 1) {\n this.maxWorkingSandboxes = navigator.hardwareConcurrency - 1;\n if (typeof navigator.userAgent === 'string') {\n if (/(Android).*(Chrome|Chromium)/.exec(navigator.userAgent)) {\n this.maxWorkingSandboxes = 1;\n console.log('Doing work with Chromimum browsers on Android is currently limited to one sandbox');\n }\n }\n }\n\n /** @type {SupervisorCache} */\n this.cache = new SupervisorCache(this);\n /** @type {object} */\n this._connections = {}; /* active DCPv4 connections */\n // Call the watchdog every 7 seconds.\n this.watchdogInterval = setInterval(() => this.watchdog(), tuning.watchdogInterval * 1000);\n if (DCP_ENV.platform === 'nodejs' && this.options.localExec) /* interval helps keep normal worker alive forever, which we don't want in localexec */\n this.watchdogInterval.unref();\n\n const ceci = this;\n\n // Initialize to null so these properties are recognized for the Supervisor class\n this.taskDistributorConnection = null;\n this.eventRouterConnection = null;\n this.resultSubmitterConnection = null;\n this.packageManagerConnection = null;\n this.openTaskDistributorConn = function openTaskDistributorConn()\n {\n let config = dcpConfig.scheduler.services.taskDistributor;\n ceci.taskDistributorConnection = new protocolV4.Connection(config, ceci.identityKeystore, connectionOptions(config.location, 'taskDistributor'));\n ceci.taskDistributorConnection.on('close', ceci.openTaskDistributorConn);\n }\n\n this.openEventRouterConn = function openEventRouterConn()\n {\n let config = dcpConfig.scheduler.services.eventRouter;\n ceci.eventRouterConnection = new protocolV4.Connection(config, ceci.identityKeystore, connectionOptions(config.location, 'eventRouter'));\n ceci.eventRouterConnection.on('close', ceci.openEventRouterConn);\n if (ceci.eventRouterMessageQueue.length)\n ceci.resendRejectedMessages(ceci.eventRouterConnection, ceci.eventRouterMessageQueue);\n }\n this.eventRouterMessageQueue = [];\n \n this.openResultSubmitterConn = function openResultSubmitterConn()\n {\n let config = dcpConfig.scheduler.services.resultSubmitter;\n ceci.resultSubmitterConnection = new protocolV4.Connection(config, ceci.identityKeystore, connectionOptions(config.location, 'resultSubmitter'));\n ceci.resultSubmitterConnection.on('close', ceci.openResultSubmitterConn);\n if (ceci.resultSubmitterMessageQueue.length)\n ceci.resendRejectedMessages(ceci.resultSubmitterConnection, ceci.resultSubmitterMessageQueue);\n }\n this.resultSubmitterMessageQueue = [];\n\n this.openPackageManagerConn = function openPackageManagerConn()\n {\n let config = dcpConfig.packageManager;\n ceci.packageManagerConnection = new protocolV4.Connection(config, ceci.identityKeystore, connectionOptions(config.location, 'packageManager'));\n ceci.packageManagerConnection.on('close', ceci.openPackageManagerConn);\n if (ceci.packageManagerMessageQueue.length)\n ceci.resendRejectedMessages(ceci.packageManagerConnection, ceci.packageManagerMessageQueue);\n }\n this.packageManagerMessageQueue = [];\n }\n\n /**\n * Return worker opaqueId.\n * @type {opaqueId}\n */\n get workerOpaqueId() {\n if (!this._workerOpaqueId)\n this._workerOpaqueId = localStorage.getItem('workerOpaqueId');\n\n if (!this._workerOpaqueId || this._workerOpaqueId.length !== constants.workerIdLength) {\n this._workerOpaqueId = generateOpaqueId();\n localStorage.setItem('workerOpaqueId', this._workerOpaqueId);\n }\n\n return this._workerOpaqueId;\n }\n\n /**\n * This getter is the absolute source-of-truth for what the\n * identity keystore is for this instance of the Supervisor.\n */\n get identityKeystore() {\n assert(this.defaultIdentityKeystore);\n\n return this._identityKeystore || this.defaultIdentityKeystore;\n }\n \n \n /** \n * Factory function which generates a list of origins which are safe to communicate \n * with for this purpose. Currently-valid purposes (more will be added):\n * - any\n * - fetchData\n * - fetchWork\n * - fetchWorkArguments\n * - sendResults\n */\n makeSafeOriginList(purpose)\n {\n var list = [];\n\n assert(Array.isArray(this.extraAllowOrigins[purpose]));\n \n if (this.extraAllowOrigins[purpose])\n list = list.concat(this.extraAllowOrigins[purpose]);\n if (dcpConfig.worker.allowOrigins[purpose])\n list = list.concat(dcpConfig.worker.allowOrigins[purpose])\n\n // In localExec, do not allow work function or arguments to come from the 'any' origins\n if (purpose !== 'any' && (!this.options.localExec || (this.options.localExec && purpose === 'sendResults')))\n {\n if (this.extraAllowOrigins.any)\n list = list.concat(this.extraAllowOrigins.any);\n if (dcpConfig.worker.allowOrigins.any)\n list = list.concat(dcpConfig.worker.allowOrigins.any);\n }\n return list;\n }\n\n /**\n * Open all connections. Used when supervisor is instantiated or stopped/started\n * to initially open connections.\n */\n instantiateAllConnections() {\n if (!this.taskDistributorConnection)\n this.openTaskDistributorConn();\n \n if (!this.eventRouterConnection)\n this.openEventRouterConn();\n \n if (!this.resultSubmitterConnection)\n this.openResultSubmitterConn();\n\n if (!this.packageManagerConnection)\n this.openPackageManagerConn();\n }\n \n /**\n * Asynchronously send a result to the result submitter that was previously rejected.\n * Different from resendRejectedMessages below in the sense that the function only resolves\n * once we've delivered the result, or gone past our max number of attempts.\n * @param {object} result \n * @returns the response payload from the result operation\n */\n async resendResult(result) {\n var protocolError = false;\n if (!result.sendRetries)\n result.sendRetries = 1;\n else\n result.sendRetries++;\n \n if (result.sendRetries > dcpConfig.worker.maxResultSubmissionRetries)\n throw new DCPError(`Could not submit result after ${dcpConfig.worker.maxResultSubmissionRetries} attempts. Aborting.`) \n \n debugging() && console.debug(`supervisor - failed to submit result ${result.sendRetries} time(s), trying again `)\n let res = await this.resultSubmitterConnection.send('result', result).catch(async (e) => {\n debugging('supervisor') && console.error(`Failed to submit result to scheduler for slice ${result.slice} of job ${result.job}:\\n ${e} \\nWill try again on new connection.`);\n this.resultSubmitterConnection.close();\n await a$sleepMs(10); /* let connection recycle */\n protocolError = true;\n });\n if ((!res.success && res.payload && res.payload.code === 'DCPS-01002') || protocolError)\n return this.resendResult(result)\n else\n return res;\n }\n \n /**\n * Try sending messages that were rejected on an old instance of the given connection.\n * These are messages that a) were rejected due to a protocol error and b) don't care when exactly\n * they're sent in the grand scheme of things.\n */\n resendRejectedMessages(connection, messageQueue) {\n var message, quitLoop;\n if (connection.resendingMessages) /* if the passed connection is already in the loop, exit */\n return;\n \n message = messageQueue.shift();\n\n do {\n connection.resendingMessages = true;\n quitLoop = false;\n \n connection.send(message.operation, message.data)\n .catch((e) =>\n {\n /* Protocol Error; Close connection (this will trigger the opening of a new connection that will try sending again) */\n debugging('supervisor') && console.error(`Failed to send message ${message.operation} to scheduler: ${e}. Will try again on a new \n connection.`);\n messageQueue.unshift(message);\n connection.close();\n quitLoop = true;\n });\n \n message = messageQueue.shift();\n \n } while (message && !quitLoop)\n\n connection.resendingMessages = false;\n }\n\n /** Set the default identity keystore -- needs to happen before anything that talks\n * to the scheduler for work gets called. This is a wart and should be removed by\n * refactoring.\n *\n * The default identity keystore will be used if the Supervisor was not provided\n * with an alternate. This keystore will be located via the Wallet API, and \n * if not found, a randomized default identity will be generated. \n *\n * @param {object} ks An instance of wallet::Keystore -- if undefined, we pick the best default we can.\n * @returns {Promise<void>}\n */\n async setDefaultIdentityKeystore(ks) {\n try {\n if (ks) {\n this.defaultIdentityKeystore = ks;\n return;\n }\n\n if (this.defaultIdentityKeystore)\n return;\n\n try {\n this.defaultIdentityKeystore = await wallet.getId();\n } catch(e) {\n debugging('supervisor') && console.debug('Error generating default identity, try to do it another way.');\n this.defaultIdentityKeystore = await new wallet.IdKeystore(null, '');\n }\n } finally {\n if (this.defaultIdentityKeystore)\n debugging('supervisor') && console.debug('Set default identity =', this.defaultIdentityKeystore.address);\n else\n debugging('supervisor') && console.debug('Failed to set default identity, worker cannot work.');\n }\n }\n\n //\n // What follows is a bunch of utility properties and functions for creating filtered views\n // of the slices and sandboxes array.\n //\n /** XXXpfr @todo Write sort w/o using promises so we can get rid of async on all the compress functions. */\n\n /**\n * @deprecated -- Please do not use this.workingSandboxes; use this.allocatedSandboxes instead.\n * Sandboxes that are in WORKING state.\n *\n * Warning: Do not rely on this information being 100% accurate -- it may change in the next instant.\n * @type {Sandbox[]}\n */\n get workingSandboxes() {\n return this.sandboxes.filter(sandbox => sandbox.isWorking);\n }\n\n /**\n * Use instead of this.workingSandboxes.\n *\n * When a sandbox is paired with a slice, execution is pending and sandbox.allocated=true and\n * sandbox.slice=slice and sandbox.jobAddress=slice.jobAddress. This is what 'allocated' means.\n * Immediately upon the exit of sandbox.work, sandbox.allocated=false is set and if an exception\n * wasn't thrown the sandbox is placed in this.assignedSandboxes.\n * Thus from the pov of supervisor, this.allocatedSandboxes is deterministic and this.workingSandboxes is not.\n * Please try to not use this.workingSandboxes. It is deprecated.\n *\n * Warning: Do not rely on this information being 100% accurate -- it may change in the next instant.\n * @type {Sandbox[]}\n */\n get allocatedSandboxes() {\n return this.sandboxes.filter(sandbox => sandbox.allocated);\n }\n\n /**\n * Slices that are allocated.\n * Warning: Do not rely on this information being 100% accurate -- it may change in the next instant.\n * @type {Slice[]}\n */\n get allocatedSlices() {\n return this.slices.filter(slice => slice.allocated);\n }\n\n /**\n * This property is used as the target number of sandboxes to be associated with slices and start working.\n *\n * It is used in this.watchdog as to prevent a call to this.work when unallocatedSpace <= 0.\n * It is also used in this.distributeQueuedSlices where it is passed as an argument to this.matchSlicesWithSandboxes to indicate how many sandboxes\n * to associate with slices and start working.\n *\n * Warning: Do not rely on this information being 100% accurate -- it may change in the next instant.\n * @type {number}\n */\n get unallocatedSpace() {\n return this.maxWorkingSandboxes - this.allocatedSandboxes.length - this.numberOfCoresReserved;\n }\n \n /**\n * Call acquire(numberOfCoresToReserve) to reserve numberOfCoresToReserve unallocated sandboxes as measured by unallocatedSpace.\n * Call release() to undo the previous acquire.\n * This pseudo-mutex technique helps prevent races in scheduling slices in Supervisor.\n * @param {number} numberOfCoresToReserve\n */\n acquire(numberOfCoresToReserve) { \n this.numberOfCoresReserved = numberOfCoresToReserve; \n }\n release() { \n this.numberOfCoresReserved = 0; \n }\n\n /**\n * Remove from this.slices.\n * @param {Slice} slice\n */\n removeSlice(slice) {\n this.removeElement(this.slices, slice);\n if (Supervisor.debugBuild) {\n if (this.queuedSlices.indexOf(slice) !== -1)\n throw new Error(`removeSlice: slice ${slice.identifier} is in queuedSlices; inconsistent state.`);\n if (this.lostSlices.length > 0) {\n console.warn(`removeSlice: slice ${slice.identifier}, found lostSlices ${this.lostSlices.map(s => s.identifier)}`);\n if (this.lostSlices.indexOf(slice) !== -1)\n throw new Error(`removeSlice: slice ${slice.identifier} is in lostSlices; inconsistent state.`);\n }\n }\n }\n\n /**\n * Remove from this.slices.\n * @param {Slice[]} slices\n */\n removeSlices(slices) {\n this.slices = this.slices.filter(slice => slices.indexOf(slice) === -1);\n }\n\n /**\n * Remove from this.queuedSlices.\n * @param {Slice[]} slices\n */\n removeQueuedSlices(slices) {\n this.queuedSlices = this.queuedSlices.filter(slice => slices.indexOf(slice) === -1);\n }\n\n /**\n * Remove from this.sandboxes, this.assignedSandboxes and this.readiedSandboxes.\n * @param {Sandbox} sandbox\n */\n removeSandbox(sandbox) {\n debugging('scheduler') && console.log(`removeSandbox ${sandbox.identifier}`);\n this.removeElement(this.sandboxes, sandbox);\n this.removeElement(this.assignedSandboxes, sandbox);\n\n // XXXpfr: April 13, 2022\n // I'm trying to understand and control when sandboxes get removed.\n // A sandbox in this.readiedSandboxes should never have returnSandbox/removeSandbox called on it except in stopWork.\n // Because of races and random worker crashes, it is hard to get this right, but I want to try.\n // If I don't fix this is the next 30 days or I forget, please delete this exception.\n if (false)\n {}\n\n this.removeElement(this.readiedSandboxes, sandbox);\n }\n\n /**\n * Remove from this.sandboxes and this.assignedSandboxes .\n * @param {Sandbox[]} sandboxes\n */\n async removeSandboxes(sandboxes) {\n debugging('scheduler') && console.log(`removeSandboxes: Remove ${sandboxes.length} sandboxes ${this.dumpSandboxes(sandboxes)}`);\n this.sandboxes = this.sandboxes.filter(sandbox => sandboxes.indexOf(sandbox) === -1);\n this.assignedSandboxes = this.assignedSandboxes.filter(sandbox => sandboxes.indexOf(sandbox) === -1);\n\n if (Supervisor.debugBuild) {\n const readied = this.readiedSandboxes.filter(sandbox => sandboxes.indexOf(sandbox) !== -1);\n if (readied.length > 0)\n throw new Error(`removeSandboxes: sandboxes ${readied.map(s => s.identifier)} are in readiedSandboxes; inconsistent state.`);\n }\n }\n\n /**\n * Remove element from theArray.\n * @param {Array<*>} theArray\n * @param {object|number} element\n * @param {boolean} [assertExists = true]\n */\n removeElement(theArray, element, assertExists = false) {\n let index = theArray.indexOf(element);\n assert(index !== -1 || !assertExists);\n if (index !== -1) theArray.splice(index, 1);\n }\n\n /**\n * Log sliceArray.\n * @param {Slice[]} sliceArray\n * @param {string} [header]\n * @returns {string}\n */\n dumpSlices(sliceArray, header) {\n if (header) console.log(`\\n${header}`);\n return compressSlices(sliceArray, this.addressTruncationLength);\n }\n\n /**\n * Log sandboxArray.\n * @param {Sandbox[]} sandboxArray\n * @param {string} [header]\n * @returns {string}\n */\n dumpSandboxes(sandboxArray, header) {\n if (header) console.log(`\\n${header}`);\n return compressSandboxes(sandboxArray, this.addressTruncationLength);\n }\n\n /**\n * If the elements of sandboxSliceArray are not unique, log the duplicates and dump the array.\n * @param {SandboxSlice[]} sandboxSliceArray\n * @param {string} header\n */\n dumpSandboxSlicesIfNotUnique(sandboxSliceArray, header) {\n if (!this.isUniqueSandboxSlices(sandboxSliceArray, header))\n console.log(this.dumpSandboxSlices(sandboxSliceArray));\n }\n\n /**\n * Log { sandbox, slice }.\n * @param {Sandbox} sandbox\n * @param {Slice} slice\n * @returns {string}\n */\n dumpSandboxAndSlice(sandbox, slice) {\n return `${sandbox.id}~${slice.sliceNumber}.${this.dumpJobAddress(slice.jobAddress)}`;\n }\n\n /**\n * Log { sandbox, slice } with state/status.\n * @param {Sandbox} sandbox\n * @param {Slice} slice\n * @returns {string}\n */\n dumpStatefulSandboxAndSlice(sandbox, slice) {\n return `${sandbox.id}.${sandbox.state}~${slice.sliceNumber}.${this.dumpJobAddress(slice.jobAddress)}.${slice.status}`;\n }\n\n /**\n * Truncates jobAddress.toString() to this.addressTruncationLength digits.\n * @param {address} jobAddress\n * @returns {string}\n */\n dumpJobAddress(jobAddress) {\n return truncateAddress(jobAddress, this.addressTruncationLength /* digits*/);\n }\n\n /**\n * Dump sandboxSliceArray.\n * @param {SandboxSlice[]} sandboxSliceArray - input array of { sandbox, slice }\n * @param {string} [header] - optional header\n * @param {boolean} [stateFul] - when true, also includes slice.status and sandbox.state.\n * @returns {string}\n */\n dumpSandboxSlices(sandboxSliceArray, header, stateFul=false) {\n if (header) console.log(`\\n${header}`);\n const jobMap = {};\n sandboxSliceArray.forEach(ss => {\n const sss = stateFul ? `${ss.sandbox.id}.${ss.sandbox.state}~${ss.slice.sliceNumber}.${ss.slice.status}` : `${ss.sandbox.id}~${ss.slice.sliceNumber}`;\n if (!jobMap[ss.slice.jobAddress]) jobMap[ss.slice.jobAddress] = sss;\n else jobMap[ss.slice.jobAddress] += `,${sss}`;\n });\n let output = '';\n for (const [jobAddress, sss] of Object.entries(jobMap))\n output += `${this.dumpJobAddress(jobAddress)}:[${sss}]:`;\n return output;\n }\n\n /**\n * Check sandboxSliceArray for duplicates.\n * @param {SandboxSlice[]} sandboxSliceArray\n * @param {string} [header]\n * @param {function} [log]\n * @returns {boolean}\n */\n isUniqueSandboxSlices(sandboxSliceArray, header, log) {\n const result = [], slices = [], sandboxes = [];\n let once = true;\n sandboxSliceArray.forEach(x => {\n const sliceIndex = slices.indexOf(x.slice);\n const sandboxIndex = sandboxes.indexOf(x.sandbox);\n\n if (sandboxIndex >= 0) {\n if (once && header) console.log(`\\n${header}`); once = false;\n log ? log(x.sandbox) : console.log(`\\tWarning: Found duplicate sandbox ${x.sandbox.identifier}.`);\n } else sandboxes.push(x.sandbox);\n\n if (sliceIndex >= 0) {\n if (once && header) console.log(`\\n${header}`); once = false;\n log ? log(x.slice) : console.log(`\\tWarning: Found duplicate slice ${x.slice.identifier}.`);\n } else {\n slices.push(x.slice);\n if (sandboxIndex < 0) result.push(x);\n }\n });\n return sandboxSliceArray.length === result.length;\n }\n\n /**\n * Attempts to create and start a given number of sandboxes.\n * The sandboxes that are created can then be assigned for a\n * specific job at a later time. All created sandboxes\n * get put into the @this.readiedSandboxes array when allocateLocalSandboxes is false.\n *\n * @param {number} numSandboxes - the number of sandboxes to create\n * @param {boolean} [allocateLocalSandboxes=false] - when true, do not place in this.readiedSandboxes\n * @returns {Promise<Sandbox[]>} - resolves with array of created sandboxes, rejects otherwise\n * @throws when given a numSandboxes is not a number or if numSandboxes is Infinity\n */\n async readySandboxes (numSandboxes, allocateLocalSandboxes = false) {\n debugging('supervisor') && console.debug(`readySandboxes: Readying ${numSandboxes} sandboxes, total sandboxes ${this.sandboxes.length}, matching ${this.matching}, fetching ${this.isFetchingNewWork}`);\n \n if (typeof numSandboxes !== 'number' || Number.isNaN(numSandboxes) || numSandboxes === Infinity) {\n throw new Error(`${numSandboxes} is not a number of sandboxes that can be readied.`);\n }\n if (numSandboxes <= 0) {\n return [];\n }\n\n const sandboxStartPromises = [];\n const sandboxes = [];\n const errors = [];\n for (let i = 0; i < numSandboxes; i++) {\n const sandbox = new Sandbox(this, {\n ...this.options.sandboxOptions,\n });\n sandbox.addListener('ready', () => this.emit('sandboxReady', sandbox));\n sandbox.addListener('start', () => {\n this.emit('sandboxStart', sandbox);\n\n // When sliceNumber == 0, result-submitter status skips the slice,\n // so don't send it in the first place.\n // The 'start' event is fired when a worker starts up, hence there's no way\n // to determine whether sandbox has a valid slice without checking.\n if (sandbox.slice) {\n const jobAddress = sandbox.jobAddress;\n const sliceNumber = sandbox.slice.sliceNumber;\n // !authorizationMessage <==> sliceNumber === 0.\n const authorizationMessage = sandbox.slice.getAuthorizationMessage();\n\n if (authorizationMessage) {\n let statusPayload = {\n worker: this.workerOpaqueId,\n slices: [{\n job: jobAddress,\n sliceNumber: sliceNumber,\n status: 'begin',\n authorizationMessage,\n }],\n }\n \n try /* resultSubmitterConnection can be null if worker is stopped */\n {\n this.resultSubmitterConnection.send('status', statusPayload).catch((error) => {\n debugging('supervisor') && console.error(`Error sending 'status' for slice ${sliceNumber} of job ${jobAddress}:\\n ${error}\\nWill try again on a new connection`);\n this.resultSubmitterMessageQueue.push({ operation: 'status', data: statusPayload });\n this.resultSubmitterConnection.close();\n });\n } catch (error)\n {\n debugging('supervisor') && console.error(`Failed to send 'status' for slice ${sliceNumber} of job ${jobAddress}:, no connection to result submitter:`, error);\n }\n }\n }\n });\n sandbox.addListener('workEmit', ({ eventName, payload }) => {\n // Need to check if the sandbox hasn't been assigned a slice yet.\n if (!sandbox.slice) {\n if (Supervisor.debugBuild) {\n console.error(\n `Sandbox not assigned a slice before sending workEmit message to scheduler. 'workEmit' event originates from \"${eventName}\" event`, \n payload,\n );\n }\n }\n else\n {\n const jobAddress = sandbox.slice.jobAddress;\n const sliceNumber = sandbox.slice.sliceNumber;\n // sliceNumber can be zero if it came from a problem with loading modules.\n assert(jobAddress && (sliceNumber || sliceNumber === 0));\n // Send a work emit message from the sandbox to the event router\n // !authorizationMessage <==> sliceNumber === 0.\n let authorizationMessage;\n try {\n // Sometimes a sliceNumber===0 workEmit comes in before the client bundle is properly loaded.\n // Also happens with minor dcp-client version mismatches.\n authorizationMessage = sandbox.slice.getAuthorizationMessage();\n } catch(e) {\n authorizationMessage = null;\n }\n\n if (!authorizationMessage)\n {\n console.warn(`workEmit: missing authorization message for job ${jobAddress}, slice: ${sliceNumber}`);\n return Promise.resolve();\n }\n \n let workEmitPayload = {\n eventName,\n payload,\n job: jobAddress,\n slice: sliceNumber,\n worker: this.workerOpaqueId,\n authorizationMessage,\n }\n \n const workEmitPromise = this.eventRouterConnection.send('workEmit', workEmitPayload).catch(error => {\n debugging('supervisor') && console.warn(`workEmit: unable to send ${eventName} for slice ${sliceNumber} of job ${jobAddress}: ${error.message}.\\nTrying again on a new connection.`);\n this.eventRouterMessageQueue.push({ operation: 'workEmit', data: workEmitPayload })\n this.eventRouterConnection.close();\n if (Supervisor.debugBuild)\n console.error('workEmit error:', error);\n });\n\n if (Supervisor.debugBuild) {\n workEmitPromise.then(result => {\n if (!result || !result.success)\n console.warn('workEmit: event router did not accept event', result);\n });\n }\n }\n });\n\n // When any sbx completes, \n sandbox.addListener('complete', () => {\n this.watchdog();\n });\n\n sandbox.on('sandboxError', (error) => handleSandboxError(this, sandbox, error));\n \n sandbox.on('rejectedWorkMetrics', (data) =>{\n function updateRejectedMetrics(report) {\n ['total', 'CPU', 'webGL'].forEach((key) => {\n if (report[key]) sandbox.slice.rejectedTimeReport[key] += report[key];\n })\n }\n \n // If the slice already has rejected metrics, add this data to it. If not, assign this data to slices rejected metrics property\n if (sandbox.slice) {\n (sandbox.slice.rejectedTimeReport) ? updateRejectedMetrics(data.timeReport) : sandbox.slice.rejectedTimeReport = data.timeReport;\n }\n })\n \n // If the sandbox terminated and we are not shutting down, then should return all work which is currently\n // not being computed if all sandboxes are dead and the attempt to create a new one fails.\n sandbox.on('terminated',async () => {\n if (this.sandboxes.length > 0) {\n let terminatedSandboxes = this.sandboxes.filter(sbx => sbx.isTerminated);\n if (terminatedSandboxes.length === this.sandboxes.length) {\n debugging('supervisor') && console.debug(`readySandboxes: Create 1 sandbox in the sandbox-terminated-handler, total sandboxes ${this.sandboxes.length}, matching ${this.matching}, fetching ${this.isFetchingNewWork}`);\n await this.readySandboxes(1);\n \n // If we cannot create a new sandbox, that probably means we're on a screensaver worker\n // and the screensaver is down. So return the slices to the scheduler.\n if (this.sandboxes.length !== terminatedSandboxes.length + 1) {\n this.returnSlices(this.queuedSlices).then(() => {\n this.queuedSlices.length = 0;\n });\n }\n }\n }\n })\n\n const delayMs =\n 1000 *\n (tuning.minSandboxStartDelay +\n Math.random() *\n (tuning.maxSandboxStartDelay - tuning.minSandboxStartDelay));\n \n sandboxStartPromises.push(\n sandbox\n .start(delayMs)\n .then(() => {\n if (!allocateLocalSandboxes) this.readiedSandboxes.push(sandbox);\n this.sandboxes.push(sandbox);\n sandboxes.push(sandbox);\n }).catch((err) => {\n errors.push(err);\n this.returnSandbox(sandbox);\n if (err.code === 'ENOWORKER') {\n throw new DCPError(\"Cannot use localExec without dcp-worker installed. Use the command 'npm install dcp-worker' to install the neccessary modules.\", 'ENOWORKER');\n }\n }));\n }\n \n await Promise.all(sandboxStartPromises);\n\n if (errors.length) {\n console.warn(`Failed to ready ${errors.length} of ${numSandboxes} sandboxes.`, errors);\n throw new Error('Failed to ready sandboxes.');\n }\n\n debugging('supervisor') && console.log(`readySandboxes: Readied ${sandboxes.length} sandboxes ${JSON.stringify(sandboxes.map(sandbox => sandbox.id))}`);\n \n return sandboxes;\n }\n\n /**\n * Accepts a sandbox after it has finished working or encounters an error.\n * If the sandbox was terminated or if \"!slice || slice.failed\" then\n * the sandbox will be removed from the sandboxes array and terminated if necessary.\n * Otherwise it will try to distribute a slice to the sandbox immediately.\n *\n * @param {Sandbox} sandbox - the sandbox to return\n * @param {Slice} [slice] - the slice just worked on; !slice => terminate\n * @param {boolean} [verifySandboxIsNotTerminated=true] - if true, check sandbox is not already terminated\n */\n returnSandbox (sandbox, slice, verifySandboxIsNotTerminated=true) {\n if (!slice || slice.failed || sandbox.isTerminated) {\n \n this.removeSandbox(sandbox);\n \n if (!sandbox.isTerminated) {\n debugging('supervisor') && console.log(`Supervisor.returnSandbox: Terminating ${sandbox.identifier}${slice ? `~${slice.identifier}` : ''}, # of sandboxes ${this.sandboxes.length}`);\n sandbox.terminate(false);\n } else {\n debugging('supervisor') && console.log(`Supervisor.returnSandbox: Already terminated ${sandbox.identifier}${slice ? `~${slice.identifier}` : ''}, # of sandboxes ${this.sandboxes.length}`);\n // XXXpfr: April 13, 2022\n // I'm trying to understand and control when sandboxes get terminated.\n // Because of races and random worker crashes, it is impossible to not try to terminate a sandbox more than once.\n // But at some places where returnSandbox is we shouldn't see this behavior, hence this exception.\n // If I don't fix this is the next 30 days or I forget, please delete this exception.\n if (false)\n {}\n }\n }\n }\n\n /**\n * Terminates sandboxes, in order of creation, when the total started sandboxes exceeds the total allowed sandboxes.\n *\n * @returns {Promise<void>}\n */\n pruneSandboxes () {\n let numOver = this.sandboxes.length - (dcpConfig.worker.maxAllowedSandboxes + this.maxWorkingSandboxes);\n if (numOver <= 0) return;\n \n // Don't kill readied sandboxes while creating readied sandboxes.\n for (let index = 0; index < this.readiedSandboxes.length; ) {\n const sandbox = this.readiedSandboxes[index];\n // If the sandbox is allocated, advance to the next one in the list.\n if (sandbox.allocated) {\n index++;\n continue;\n }\n // Otherwise, remove this sandbox but look at the same array index in the next loop.\n debugging('supervisor') && console.log(`pruneSandboxes: Terminating readied sandbox ${sandbox.identifier}`);\n this.readiedSandboxes.splice(index, 1);\n this.returnSandbox(sandbox);\n\n if (--numOver <= 0) break;\n }\n\n if (numOver <= 0) return;\n for (let index = 0; index < this.assignedSandboxes.length; ) {\n const sandbox = this.assignedSandboxes[index];\n // If the sandbox is allocated, advance to the next one in the list.\n if (sandbox.allocated) {\n index++;\n continue;\n }\n // Otherwise, remove this sandbox but look at the same array index in the next loop.\n debugging('supervisor') && console.log(`pruneSandboxes: Terminating assigned sandbox ${sandbox.identifier}`);\n this.assignedSandboxes.splice(index, 1);\n this.returnSandbox(sandbox);\n\n if (--numOver <= 0) break;\n }\n }\n \n /**\n * Basic watch dog to check if there are idle sandboxes and\n * attempts to nudge the supervisor to feed them work.\n *\n * Run in an interval created in @constructor .\n * @returns {Promise<void>}\n */\n async watchdog () {\n if (!this.watchdogState)\n this.watchdogState = {};\n\n // Every 5 minutes, report progress of all working slices to the scheduler\n if (Date.now() > ((this.lastProgressReport || 0) + sandboxTuning.progressReportInterval)) {\n // console.log('454: Assembling progress update...');\n this.lastProgressReport = Date.now();\n\n //\n // Note: this.slices is the disjoint union of:\n // this.allocatedSlices, \n // this.queuedSlices, \n // this.slices.filter(slice => !slice.isUnassigned) .\n // When a slice is not in these 3 arrays, the slice is lost.\n //\n \n const currentLostSlices = this.slices.filter(slice => slice.isUnassigned \n && this.queuedSlices.indexOf(slice) === -1\n && this.allocatedSlices.indexOf(slice) === -1);\n\n if (currentLostSlices.length > 0) {\n this.lostSlices.push(...currentLostSlices);\n // Try to recover.\n // Needs more work and testing.\n // Test when we can come up with a decent lost slice repro case.\n // --> this.queuedSlices.push(...currentLostSlices);\n }\n\n if (this.lostSlices.length > 0) {\n if (true) { // Keep this on for awhile, until we know lost slices aren't happening.\n console.warn('Supervisor.watchdog: Found lost slices!');\n for (const slice of this.lostSlices)\n console.warn('\\t', slice.identifier);\n }\n this.lostSlices = this.lostSlices.filter(slice => slice.isUnassigned);\n }\n\n const slices = [];\n this.queuedSlices.forEach(slice => {\n assert(slice && slice.sliceNumber > 0);\n addToSlicePayload(slices, slice, sliceStatus.scheduled);\n });\n\n this.allocatedSlices.forEach(slice => {\n assert(slice && slice.sliceNumber > 0);\n addToSlicePayload(slices, slice, 'progress'); // Beacon.\n });\n\n if (slices.length) {\n // console.log('471: sending progress update...');\n const progressReportPayload = {\n worker: this.workerOpaqueId,\n slices,\n };\n\n this.resultSubmitterConnection.send('status', progressReportPayload)\n .catch(error => {\n debugging('supervisor') && console.error('479: Failed to send status update:', error/*.message*/);\n this.resultSubmitterMessageQueue.push({ operation: 'status', data: progressReportPayload })\n this.resultSubmitterConnection.close();\n });\n }\n }\n\n if (this.worker.working) {\n if (this.unallocatedSpace > 0) {\n await this.work().catch(err => {\n if (!this.watchdogState[err.code || '0'])\n this.watchdogState[err.code || '0'] = 0;\n if (Date.now() - this.watchdogState[err.code || '0'] > ((dcpConfig.worker.watchdogLogInterval * timeDilation || 120) * 1000))\n console.error('301: Failed to start work:', err);\n this.watchdogState[err.code || '0'] = Date.now();\n });\n }\n\n this.pruneSandboxes();\n }\n }\n\n /**\n * Gets the logical and physical number of cores and also\n * the total number of sandboxes the worker is allowed to run\n *\n */\n getStatisticsCPU() {\n if (DCP_ENV.isBrowserPlatform) {\n return {\n worker: this.workerOpaqueId,\n lCores: window.navigator.hardwareConcurrency,\n pCores: dcpConfig.worker.pCores || window.navigator.hardwareConcurrency,\n sandbox: this.maxWorkingSandboxes\n }\n }\n\n return {\n worker: this.workerOpaqueId,\n lCores: requireNative('os').cpus().length,\n pCores: requireNative('physical-cpu-count'),\n sandbox: this.maxWorkingSandboxes\n }\n }\n\n /**\n * Returns the number of unallocated sandbox slots to send to fetchTask.\n *\n * @returns {number}\n */\n numberOfAvailableSandboxSlots() {\n let numCores;\n if (this.options.priorityOnly && this.options.jobAddresses.length === 0) {\n numCores = 0;\n } else if (this.queuedSlices.length > 1) {\n // We have slices queued, no need to fetch\n numCores = 0;\n } else {\n // The queue is almost empty (there may be 0 or 1 element), fetch a full task.\n // The task is full, in the sense that it will contain slices whose\n // aggregate execution time is this.maxWorkingSandboxes * 5-minutes.\n // However, there can only be this.unallocatedSpace # of long slices.\n // Thus we need to know whether the last slice in this.queuedSlices is long or not.\n // (A long slice has estimated execution time >= 5-minutes.)\n const longSliceCount = (this.queuedSlices.length > 0 && this.queuedSlices[0].isLongSlice) ? 1 : 0;\n numCores = this.unallocatedSpace - longSliceCount;\n }\n return numCores;\n }\n\n /**\n * Call to start doing work on the network.\n * This is the one place where requests to fetch new slices are made.\n * After the initial slices are fetched it calls this.distributeQueuedSlices.\n *\n * @returns {Promise<void>}, unallocatedSpace ${this.unallocatedSpace}\n */\n async work()\n {\n // When inside matchSlicesWithSandboxes, don't reenter Supervisor.work to fetch new work or create new sandboxes.\n if (this.matching) {\n // Interesting and noisy.\n // debugging('supervisor') && console.log(`Supervisor.work: Do not interleave work, fetch or matching slices with sandboxes: queuedSlices ${this.queuedSlices.length}, unallocatedSpace ${this.unallocatedSpace}, matching ${this.matching}, fetching ${this.isFetchingNewWork}`);\n return Promise.resolve();\n }\n\n await this.setDefaultIdentityKeystore();\n\n // Instantiate connections that don't exist.\n this.instantiateAllConnections();\n\n const numCores = this.numberOfAvailableSandboxSlots();\n\n debugging() && console.log(`Supervisor.work: Try to get ${numCores} slices in working sandboxes, unallocatedSpace ${this.unallocatedSpace}, queued slices ${this.queuedSlices.length}, # of sandboxes ${this.sandboxes.length}, matching ${this.matching}, fetching: ${this.isFetchingNewWork}`);\n \n // Fetch a new task if we have no more slices queued, then start workers\n try {\n if (numCores > 0 && !this.isFetchingNewWork) {\n this.isFetchingNewWork = true;\n\n /**\n * This will only ready sandboxes up to a total count of\n * maxWorkingSandboxes (in any state). It is not possible to know the\n * actual number of sandboxes required until we have the slices because we\n * may have sandboxes assigned for the slice's job already.\n *\n * If the evaluator cannot start (ie. if the evalServer is not running),\n * then the while loop will keep retrying until the evalServer comes online\n */\n if (this.maxWorkingSandboxes > this.sandboxes.length) {\n // Note: The old technique had \n // while (this.maxWorkingSandboxes > this.sandboxes.length) {....\n // and sometimes we'd get far too many sandboxes, because it would keep looping while waiting for\n // this.readySandboxes(this.maxWorkingSandboxes - this.sandboxes.length);\n // to construct the rest of the sandboxes. The fix is to only loop when the 1st \n // await this.readySandboxes(1) \n // is failing.\n let needFirstSandbox = true;\n while (needFirstSandbox) {\n debugging('supervisor') && console.log(`Supervisor.work: ready 1 sandbox, # of sandboxes ${this.sandboxes.length}, matching ${this.matching}, fetching ${this.isFetchingNewWork}`);\n await this.readySandboxes(1)\n .then(() => {\n debugging('supervisor') && console.log(`Supervisor.work: ready ${this.maxWorkingSandboxes - this.sandboxes.length} sandbox(es), # of sandboxes ${this.sandboxes.length}, matching ${this.matching}, fetching ${this.isFetchingNewWork}`);\n this.readySandboxes(this.maxWorkingSandboxes - this.sandboxes.length);\n needFirstSandbox = false;\n }).catch(error => {\n console.warn('906: failed to ready sandboxes; will retry', error.code, error.message);\n });\n }\n }\n\n /**\n * Temporary change: Assign the capabilities of one of readied sandboxes\n * before fetching slices from the scheduler.\n *\n * TODO: Remove this once fetchTask uses the capabilities of every\n * sandbox to fetch slices.\n */\n if (!this.capabilities) {\n this.capabilities = this.sandboxes[0].capabilities;\n this.emit('capabilitiesCalculated', this.capabilities);\n }\n\n if (DCP_ENV.isBrowserPlatform && this.capabilities.browser)\n this.capabilities.browser.chrome = DCP_ENV.isBrowserChrome;\n\n const fetchTimeout = setTimeout(() => {\n console.warn(`679: Fetch exceeded timeout, will reconnect at next watchdog interval`);\n \n this.taskDistributorConnection.close('Fetch timed out', Math.random() > 0.5).catch(error => {\n console.error(`931: Failed to close task-distributor connection`, error);\n });\n this.resultSubmitterConnection.close('Fetch timed out', Math.random() > 0.5).catch(error => {\n console.error(`920: Failed to close result-submitter connection`, error);\n });\n this.isFetchingNewWork = false;\n this.instantiateAllConnections();\n }, 3 * 60 * 1000); // max out at 3 minutes to fetch\n\n // ensure result submitter and task distributor connections before fetching tasks\n try\n {\n await this.resultSubmitterConnection.keepalive();\n await this.taskDistributorConnection.keepalive();\n }\n catch (e)\n {\n console.error('Failed to connect to result submitter, refusing to fetch slices. Will try again at next fetch cycle.')\n debugging('supervisor') && console.log(`Error: ${e}`);\n this.isFetchingNewWork = false; // <-- done in the `finally` block, below\n clearTimeout(fetchTimeout);\n this.taskDistributorConnection.close('Failed to connect to result-submitter', true).catch(error => {\n console.error(`939: Failed to close task-distributor connection`, error);\n });\n this.resultSubmitterConnection.close('Failed to connect to result-submitter', true).catch(error => {\n console.error(`942: Failed to close result-submitter connection`, error);\n });\n return Promise.resolve();\n }\n await this.fetchTask(numCores).finally(() => {\n clearTimeout(fetchTimeout);\n this.isFetchingNewWork = false;\n });\n }\n\n this.distributeQueuedSlices().then(() => debugging('supervisor') && 'supervisor: finished distributeQueuedSlices()').catch((e) => {\n // We should never get here, because distributeQueuedSlices was changed\n // to try to catch everything and return slices and sandboxes.\n // If we do catch here it may mean a slice was lost. \n console.error('Supervisor.work catch handler for distributeQueuedSlices.', e);\n });\n // No catch(), because it will bubble outward to the caller\n } finally {\n }\n }\n\n /**\n * Generate the workerComputeGroups property of the requestTask message. \n * \n * Concatenate the compute groups object from dcpConfig with the list of compute groups\n * from the supervisor, and remove the public group if accidentally present. Finally,\n * we transform joinSecrets/joinHash into joinHashHash for secure transmission.\n *\n * @note computeGroup objects with joinSecrets are mutated to record their hashes. This\n * affects the supervisor options and dcpConfig. Re-adding a joinSecret property\n * to one of these will cause the hash to be recomputed.\n */\n generateWorkerComputeGroups()\n {\n var computeGroups = Object.values(dcpConfig.worker.computeGroups || {});\n if (this.options.computeGroups)\n computeGroups = computeGroups.concat(this.options.computeGroups);\n computeGroups = computeGroups.filter(group => group.id !== constants.computeGroups.public.id);\n const hashedComputeGroups = [];\n for (const group of computeGroups)\n {\n const groupCopy = Object.assign({}, group);\n if ((group.joinSecret || group.joinHash) && (!group.joinHashHash || this.lastDcpsid !== this.taskDistributorConnection.dcpsid))\n {\n let joinHash;\n if (group.joinHash) {\n joinHash = group.joinHash.replace(/\\s+/g, ''); // strip whitespace\n } else {\n joinHash = calculateJoinHash(groupCopy);\n } \n\n groupCopy.joinHashHash = hash.calculate(hash.eh1, joinHash, this.taskDistributorConnection.dcpsid);\n delete groupCopy.joinSecret;\n delete groupCopy.joinHash;\n debugging('computeGroups') && console.debug(`Calculated joinHash=${joinHash} for`, groupCopy);\n }\n hashedComputeGroups.push(groupCopy);\n }\n this.lastDcpsid = this.taskDistributorConnection.dcpsid;\n debugging('computeGroups') && console.debug('Requesting ', computeGroups.length, 'non-public groups for session', this.lastDcpsid);\n return hashedComputeGroups;\n }\n\n /**\n * Remove all unreferenced jobs in `this.cache`.\n *\n * @param {any[]} newJobs - Jobs that should not be removed from\n * `this.cache`.\n */\n cleanJobCache(newJobs = []) {\n /* Delete all jobs in the supervisorCache that are not represented in this newJobs,\n * or in this.queuedSlices, or there is no sandbox assigned to these jobs.\n * Note: There can easily be 200+ places to check; using a lookup structure to maintain O(n).\n */\n if (this.cache.jobs.length > 0) {\n const jobAddressMap = {};\n Object.keys(newJobs).forEach(jobAddress => { jobAddressMap[jobAddress] = 1; });\n this.slices.forEach(slice => { if (!jobAddressMap[slice.jobAddress]) jobAddressMap[slice.jobAddress] = 1; });\n this.cache.jobs.forEach(jobAddress => {\n if (!jobAddressMap[jobAddress]) {\n this.cache.remove('job', jobAddress);\n // Remove and return the corresponding sandboxes from this.sandboxes.\n const deadSandboxes = this.sandboxes.filter(sb => sb.jobAddress === jobAddress);\n if (deadSandboxes.length > 0) {\n deadSandboxes.forEach(sandbox => { this.returnSandbox(sandbox); });\n debugging('supervisor') && console.log(`Supervisor.fetchTask: Deleting job ${jobAddress} from cache and assigned sandboxes ${deadSandboxes.map(s => s.id)}, # of sandboxes ${this.sandboxes.length}.`);\n }\n }\n });\n }\n }\n\n /**\n * Fetches a task, which contains job information and slices for sandboxes and\n * manages events related to fetching tasks so the UI can more clearly display\n * to user what is actually happening.\n * @param {number} numCores\n * @returns {Promise<void>} The requestTask request, resolve on success, rejects otherwise.\n * @emits Supervisor#fetchingTask\n * @emits Supervisor#fetchedTask\n */\n async fetchTask(numCores) {\n\n // Don't reenter\n if (this.matching || numCores <= 0) {\n // Interesting and noisy.\n debugging('supervisor') && console.log(`Supervisor.fetchTask: Do not nest work, fetch or matching slices with sandboxes: queuedSlices ${this.queuedSlices.length}, unallocatedSpace ${this.unallocatedSpace}, matching ${this.matching}, fetching ${this.isFetchingNewWork}, numCores ${numCores}`);\n return Promise.resolve();\n }\n\n //\n // Oversubscription mitigation.\n // Update when there are less available sandbox slots than numCores.\n const checkNumCores = this.numberOfAvailableSandboxSlots();\n if (numCores > checkNumCores) numCores = checkNumCores;\n if (numCores <= 0) return Promise.resolve();\n\n this.emit('fetchingTask');\n debugging('supervisor') && console.debug('supervisor: fetching task');\n const requestPayload = {\n numCores,\n coreStats: this.getStatisticsCPU(),\n numGPUs: this.defaultMaxGPUs,\n capabilities: this.capabilities,\n paymentAddress: this.paymentAddress,\n jobAddresses: this.options.jobAddresses || [], // force array; when set, only fetches slices for these jobs\n localExec: this.options.localExec,\n workerComputeGroups: this.generateWorkerComputeGroups(),\n minimumWage: dcpConfig.worker.minimumWage || this.options.minimumWage,\n readyJobs: [ /* list of jobs addresses XXXwg */ ],\n previouslyWorkedJobs: this.ringBufferofJobs.buf, //Only discrete jobs\n rejectedJobs: this.rejectedJobs,\n };\n // workers should be part of the public compute group by default\n if (!booley(dcpConfig.worker.leavePublicGroup) && !booley(this.options.leavePublicGroup) && (!requestPayload.localExec))\n requestPayload.workerComputeGroups.push(constants.computeGroups.public);\n debugging('computeGroups') && console.log(`Fetching work for ${requestPayload.workerComputeGroups.length} ComputeGroups: `, requestPayload.workerComputeGroups);\n debugging('supervisor') && console.log(`fetchTask wants ${numCores} slice(s), unallocatedSpace ${this.unallocatedSpace}, queuedSlices ${this.queuedSlices.length}`);\n try {\n debugging('requestTask') && console.debug('fetchTask: requestPayload', requestPayload);\n\n let result = await this.taskDistributorConnection.send('requestTask', requestPayload).catch((error) => {\n debugging('supervisor') && console.error(`Unable to request task from scheduler: ${error}. Will try again on a new connection.`);\n this.taskDistributorConnection.close(error, true);\n throw error; /* caught below */\n });\n let responsePayload = result.payload; \n\n if (!result.success) {\n debugging() && console.log('Task fetch failure; request=', requestPayload);\n debugging() && console.log('Task fetch failure; response=', result.payload);\n throw new DCPError('Unable to fetch task for worker', responsePayload);\n }\n\n const sliceCount = responsePayload.body.task.length || 0;\n\n /**\n * The fetchedTask event fires when the supervisor has finished trying to\n * fetch work from the scheduler (task-manager). The data emitted is the\n * number of new slices to work on in the fetched task.\n *\n * @event Supervisor#fetchedTask\n * @type {number}\n */\n this.emit('fetchedTask', sliceCount);\n\n if (sliceCount < 1) {\n return Promise.resolve();\n }\n\n /**\n * DCP-1698 Send auth msg with tasks to worker, then validate authority of worker to send slice info back to scheduler.\n * payload structure: { owner: this.address, signature: signature, auth: messageLightWeight, body: messageBody };\n * messageLightWeight: { workerId: worker, jobSlices, schedulerId, jobCommissions }\n * messageBody: { newJobs: await getNewJobsForTask(dbScheduler, task, request), task }\n */\n const { body, ...authorizationMessage } = responsePayload;\n const { newJobs, task } = body;\n assert(newJobs); // It should not be possible to have !newJobs -- we throw on !success.\n \n /*\n * Ensure all jobs received from the scheduler are:\n * 1. If we have specified specific jobs the worker may work on, the received jobs are in the specified job list\n * 2. If we are in localExec, at most 1 unique job type was received (since localExec workers are designated for only\n * one job)\n * If the received jobs are not within these parameters, stop the worker since the scheduler cannot be trusted at that point.\n */\n if ((this.options.jobAddresses.length && !Object.keys(newJobs).every((ele) => this.options.jobAddresses.includes(ele)))\n || (this.options.localExec && Object.keys(newJobs).length > 1))\n {\n console.error(\"Worker received slices it shouldn't have. Rejecting the work and stopping.\");\n process.exit(1);\n }\n\n debugging() && console.log(`Supervisor.fetchTask: task: ${task.length}/${numCores}, jobs: ${Object.keys(newJobs).length}, authSlices: ${compressJobMap(authorizationMessage.auth.authSlices, true /* skipFirst*/, this.addressTruncationLength /* digits*/)}`);\n // Delete all jobs in the supervisorCache that are not represented in this task,\n // or in this.queuedSlices, or there is no sandbox assigned to these jobs.\n this.cleanJobCache(newJobs);\n\n for (const jobAddress of Object.keys(newJobs))\n if (!this.cache.cache.job[jobAddress])\n this.cache.store('job', jobAddress, newJobs[jobAddress]);\n\n // Memoize authMessage onto the Slice object, this should\n // follow it for its entire life in the worker.\n const tmpQueuedSlices = task.map(taskElement => new Slice(taskElement, authorizationMessage));\n\n // Make sure old stuff is up front.\n // matchSlicesWithSandboxes dequeues this.queuedSlices as follows:\n // slicesToMatch = this.queuedSlices.slice(0, numCores);\n this.slices.push(...tmpQueuedSlices);\n this.queuedSlices.push(...tmpQueuedSlices);\n \n // Populating the ring buffer based on job's discrete property \n Object.values(newJobs).forEach(job => {\n if(job.requirements.discrete && this.ringBufferofJobs.find(element => element === job.address) === undefined) {\n this.ringBufferofJobs.push(job.address);\n }\n });\n \n } catch (error) {\n this.emit('fetchTaskFailed', error);\n debugging('supervisor') && console.debug(`Supervisor.fetchTask failed!: error: ${error}`);\n }\n }\n\n /**\n * For each slice in this.queuedSlices, match with a sandbox in the following order:\n * 1. Try to find an already assigned sandbox in this.assignedSandboxes for the slice's job.\n * 2. Find a ready sandbox in this.readiedSandboxes that is unassigned.\n * 3. Ready a new sandbox and use that.\n *\n * Take great care in assuring sandboxes and slices are uniquely associated, viz.,\n * a given slice cannot be associated with multiple sandboxes and a given sandbox cannot be associated with multiple slices.\n * The lack of such uniqueness has been the root cause of several difficult bugs.\n *\n * Note: When a sandbox is paired with a slice, execution is pending and sandbox.allocated=true and\n * sandbox.slice=slice and sandbox.jobAddress=slice.jobAddress. This is what 'allocated' means.\n * Immediately upon the exit of sandbox.work, sandbox.allocated=false is set and if an exception\n * wasn't thrown, the paired slice is placed in this.assignedSandboxes.\n * Thus from the pov of supervisor, this.allocatedSandboxes is deterministic and this.workingSandboxes is not.\n * Please try to not use this.workingSandboxes. It is deprecated.\n *\n * The input is numCores, this,queuedSlices, this.assignedSandboxes and this.readiedSandboxes.\n * If there are not enough sandboxes, new readied sandboxes will be created using\n * await this.readySandboxes(...)\n * And it is this await boundary that has caused many bugs.\n * We try not to make assumptions about non-local state across the await boundary.\n *\n * @param {number} numCores - The number of available sandbox slots.\n * @param {boolean} [throwExceptions=true] - Whether to throw exceptions when checking for sanity.\n * @returns {Promise<SandboxSlice[]>} Returns SandboxSlice[], may have length zero.\n */\n async matchSlicesWithSandboxes (numCores, throwExceptions = true) {\n\n const sandboxSlices = [];\n if (this.queuedSlices.length === 0 || this.matching || numCores <= 0) {\n // Interesting and noisy.\n // debugging('supervisor') && console.log(`Supervisor.matchSlicesWithSandboxes: Do not nest work, fetch or matching slices with sandboxes: queuedSlices ${this.queuedSlices.length}, unallocatedSpace ${this.unallocatedSpace}, matching ${this.matching}, fetching ${this.isFetchingNewWork}, numCores ${numCores}`);\n return sandboxSlices;\n }\n\n //\n // Oversubscription mitigation.\n // Update when there are less available sandbox slots than numCores.\n // We cannot use this.unallocatedSpace here because its value is artificially low or zero, because in\n // this.distributedQueuedSlices we use the pseudo-mutex trick: this.acquire(howManySandboxSlotsToReserve)/this.release().\n // Note: Do not use this.numberOfCoresReserved outside of a function locked with this.acquire(howManySandboxSlotsToReserve) .\n const checkNumCores = this.numberOfCoresReserved; // # of locked sandbox slots.\n if (numCores > checkNumCores) numCores = checkNumCores;\n if (numCores <= 0) return sandboxSlices;\n\n // Don't ask for more than we have.\n if (numCores > this.queuedSlices.length)\n numCores = this.queuedSlices.length;\n\n debugging('supervisor') && console.log(`matchSlicesWithSandboxes: numCores ${numCores}, queued slices ${this.queuedSlices.length}: assigned ${this.assignedSandboxes.length}, readied ${this.readiedSandboxes.length}, unallocated ${this.unallocatedSpace}, # of sandboxes: ${this.sandboxes.length}`);\n\n if (debugging('supervisor')) {\n dumpSlicesIfNotUnique(this.queuedSlices, 'Warning: this.queuedSlices slices are not unique -- this is ok when slice is rescheduled.');\n dumpSandboxesIfNotUnique(this.readiedSandboxes, 'Warning: this.readiedSandboxes sandboxes are not unique!');\n dumpSandboxesIfNotUnique(this.assignedSandboxes, 'Warning: this.assignedSandboxes sandboxes are not unique!');\n }\n\n // Three functions to validate slice and sandbox.\n function checkSlice(slice, checkAllocated=true) {\n if (!slice.isUnassigned) throw new DCPError(`Slice must be unassigned: ${slice.identifier}`);\n if (checkAllocated && slice.allocated) throw new DCPError(`Slice must not already be allocated: ${slice.identifier}`);\n }\n function checkSandbox(sandbox, isAssigned) {\n if (sandbox.allocated) throw new DCPError(`Assigned sandbox must not be already allocated: ${sandbox.identifier}`);\n if (isAssigned && !sandbox.isAssigned) throw new DCPError(`Assigned sandbox is not marked as assigned: ${sandbox.identifier}`);\n if (!isAssigned && !sandbox.isReadyForAssign) throw new DCPError(`Readied sandbox is not marked as ready for assign: ${sandbox.identifier}`);\n }\n\n // Sanity checks.\n if (throwExceptions) {\n this.assignedSandboxes.forEach(sandbox => { checkSandbox(sandbox, true /* isAssigned*/); });\n this.readiedSandboxes.forEach(sandbox => { checkSandbox(sandbox, false /* isAssigned*/); });\n this.queuedSlices.forEach(slice => { checkSlice(slice); });\n } else {\n this.assignedSandboxes = this.assignedSandboxes.filter(sandbox => !sandbox.allocated && sandbox.isAssigned);\n this.readiedSandboxes = this.readiedSandboxes.filter(sandbox => !sandbox.allocated && sandbox.isReadyForAssign);\n this.queuedSlices = this.queuedSlices.filter(slice => !slice.allocated && slice.isUnassigned);\n }\n\n const sandboxKind = {\n assigned: 0,\n ready: 1,\n new: 2,\n };\n\n const ceci = this;\n /**\n * Auxiliary function to pair a sandbox with a slice and mark the sandbox as allocated.\n * An allocated sandbox is reserved and will not be released until the slice completes execution on the sandbox.\n *\n * @param {Sandbox} sandbox\n * @param {Slice} slice\n * @param {number} kind\n */\n function pair(sandbox, slice, kind) {\n checkSandbox(sandbox, kind === sandboxKind.assigned);\n checkSlice(slice, kind === sandboxKind.assigned);\n slice.allocated = true;\n sandbox.allocated = true;\n sandbox.jobAddress = slice.jobAddress; // So we can know which jobs to not delete from this.cache .\n sandbox.slice = slice;\n sandboxSlices.push({ sandbox, slice });\n if (Supervisor.sliceTiming) slice['pairingDelta'] = Date.now();\n if (debugging('supervisor')) {\n let fragment = 'New readied';\n if (kind === sandboxKind.assigned) fragment = 'Assigned';\n else if (kind === sandboxKind.ready) fragment = 'Readied';\n console.log(`matchSlicesWithSandboxes.pair: ${fragment} sandbox matched ${ceci.dumpSandboxAndSlice(sandbox, slice)}`);\n }\n }\n\n // These three arrays are used to track/store slices and sandboxes,\n // so that when an exception occurs, the following arrays are restored:\n // this.queuedSlices, this.assignedSandboxes, this.realizedSandboxes.\n let slicesToMatch = [];\n let trackAssignedSandboxes = [];\n let trackReadiedSandboxes = [];\n try\n {\n this.matching = true;\n\n let assignedCounter = 0; // How many assigned sandboxes are being used.\n let readyCounter = 0; // How many sandboxes used from the existing this.readiedSandboxes.\n let newCounter = 0; // How many sandboxes that needed to be newly created.\n\n //\n // The Ideas:\n // 1) We match each slice with a sandbox. First we match with assigned sandboxes in the order\n // that they appear in this.queuedSlices. Then we match in-order with existing this.readiedSandboxes\n // Then we match in-order with new new readied sandboxes created through\n // await this.readySandboxes(newCounter, true /* allocateLocalSandboxes*/);\n // This allows us to try different orderings of execution of slices. E.g. Wes suggested\n // trying to execute slices from different jobs with maximal job diversity -- specifically\n // if there are 3 jobs j1,j2,j3, with slices s11, s12 from j1, s21, s22, s23 from j2 and\n // s31, s32 from j3, then we try to schedule, in order s11, s21, s31, s12, s22, s32, s23.\n //\n // 2) Before matching slices with sandboxes, we allocate available assigned and readied sandboxes\n // and if more are needed then we create and allocate new ones.\n //\n // 3) Finally we match slices with sandboxes and return an array of sandboxSlice pairs.\n //\n // Note: The ordering of sandboxSlices only partially corresponds to the order of this.queuedSlices.\n // It's easy to do. When pairing with assigned sandboxes, any slice in this.queuedSlices which doesn't\n // have an assigned sandbox, will add null to the sandboxSlices array. Then when pairing with readied sandboxes,\n // we fill-in the null entries in the sandboxSlices array.\n //\n /** XXXpfr @todo When it is needed, fix the ordering as described above. */\n\n // Get the slices that are being matched.\n slicesToMatch = this.queuedSlices.slice(0, numCores);\n this.queuedSlices = this.queuedSlices.slice(numCores);\n\n debugging('supervisor') && console.log(`matchSlicesWithSandboxes: slicesToMatch ${this.dumpSlices(slicesToMatch)}`);\n\n // Create object map: jobAddress -> sandboxes with sandboxes.jobAddress === jobAddress .\n const jobSandboxMap = toJobMap(this.assignedSandboxes, sandbox => sandbox);\n \n // Create array to hold slices which do not have assigned sandboxes.\n // These slices will need to be paired with existing and possibly new readied sandboxes.\n // Specifically, the sandboxes from existing this.readiedSandboxes and new sandboxes\n // created through await this.readySandboxes(newCounter, true /* allocateLocalSandboxes*/);\n const slicesThatNeedSandboxes = [];\n\n // Pair assigned sandboxes with slices.\n for (const slice of slicesToMatch) {\n const assigned = jobSandboxMap[slice.jobAddress];\n if (assigned && assigned.length > 0) {\n // Pair.\n const sandbox = assigned.pop();\n pair(sandbox, slice, sandboxKind.assigned);\n this.removeElement(this.assignedSandboxes, sandbox);\n // Track.\n trackAssignedSandboxes.push(sandbox);\n assignedCounter++;\n } else {\n // Don't lose track of these slices.\n slice.allocated = true;\n slicesThatNeedSandboxes.push(slice);\n }\n }\n\n // Pair readied sandboxes with slices.\n readyCounter = Math.min(slicesThatNeedSandboxes.length, this.readiedSandboxes.length);\n newCounter = slicesThatNeedSandboxes.length - readyCounter;\n // Track.\n trackReadiedSandboxes = this.readiedSandboxes.slice(0, readyCounter);\n this.readiedSandboxes = this.readiedSandboxes.slice(readyCounter);\n for (const sandbox of trackReadiedSandboxes) {\n // Pair.\n const slice = slicesThatNeedSandboxes.pop();\n pair(sandbox, slice, sandboxKind.ready);\n }\n \n debugging('supervisor') && console.log(`matchSlicesWithSandboxes: assignedCounter ${assignedCounter}, readyCounter ${readyCounter}, newCounter ${newCounter}, numCores ${numCores}`)\n\n // Validate algorithm consistency.\n if (Supervisor.debugBuild && assignedCounter + readyCounter + newCounter !== numCores) {\n // Structured assert.\n throw new DCPError(`matchSlicesWithSandboxes: Algorithm is corrupt ${assignedCounter} + ${readyCounter} + ${newCounter} !== ${numCores}`);\n }\n\n // Here is an await boundary.\n // Accessing non-local data across an await boundary may result in the unexpected.\n\n // Create new readied sandboxes to associate with slicesThatNeedSandboxes.\n if (newCounter > 0) {\n // When allocateLocalSandboxes is true, this.readySandboxes does not place the new sandboxes\n // on this.readiedSandboxes. Hence the new sandboxes are private and nobody else can see them.\n debugging('supervisor') && console.log(`matchSlicesWithSandboxes: creating ${newCounter} new sandboxes, # of sandboxes ${this.sandboxes.length}`);\n const readied = await this.readySandboxes(newCounter, true /* allocateLocalSandboxes*/);\n // Track.\n trackReadiedSandboxes.push(...readied);\n\n for (const sandbox of readied) {\n assert(slicesThatNeedSandboxes.length > 0);\n // Pair\n const slice = slicesThatNeedSandboxes.pop();\n pair(sandbox, slice, sandboxKind.new);\n }\n \n // Put back any extras. There should not be any unless readySandboxes returned less than asked for.\n if (slicesThatNeedSandboxes.length > 0) {\n slicesThatNeedSandboxes.forEach(slice => {\n slice.allocated = false;\n this.queuedSlices.push(slice);\n });\n }\n }\n\n if ( false || debugging()) {\n console.log(`matchSlicesWithSandboxes: Matches: ${ this.dumpSandboxSlices(sandboxSlices) }`);\n this.dumpSandboxSlicesIfNotUnique(sandboxSlices, 'Warning: sandboxSlices; { sandbox, slice } pairs are not unique!');\n }\n } catch (e) {\n // Clear allocations.\n slicesToMatch.forEach(slice => { slice.allocated = false; });\n trackAssignedSandboxes.forEach(sandbox => { sandbox.allocated = false; sandbox.slice = null; });\n trackReadiedSandboxes.forEach(sandbox => { sandbox.allocated = false; sandbox.slice = null; sandbox.jobAddress = null; });\n \n // Filter out redundancies -- there shouldn't be any...\n slicesToMatch = slicesToMatch.filter(slice => this.queuedSlices.indexOf(slice) === -1);\n trackAssignedSandboxes = trackAssignedSandboxes.filter(sb => this.assignedSandboxes.indexOf(sb) === -1);\n trackReadiedSandboxes = trackReadiedSandboxes.filter(sb => this.readiedSandboxes.indexOf(sb) === -1);\n\n // Sanity checks.\n slicesToMatch.forEach(slice => { checkSlice(slice) });\n trackAssignedSandboxes.forEach(sandbox => { checkSandbox(sandbox, true /* isAssigned*/); });\n trackReadiedSandboxes.forEach(sandbox => { checkSandbox(sandbox, false /* isAssigned*/); });\n\n // Restore arrays.\n this.queuedSlices.push(...slicesToMatch);\n this.assignedSandboxes.push(...trackAssignedSandboxes);\n this.readiedSandboxes.push(...trackReadiedSandboxes);\n \n console.error('Error in matchSlicesWithSandboxes: Attempting to recover slices and sandboxes.', e);\n return [];\n } finally {\n this.matching = false;\n }\n\n debugging('supervisor') && console.log(`matchSlicesWithSandboxes: allocated ${sandboxSlices.length} sandboxes, queuedSlices ${this.queuedSlices.length}, unallocatedSpace ${this.unallocatedSpace}, matching ${this.matching}, fetching ${this.isFetchingNewWork}, # of sandboxes: ${this.sandboxes.length}.`);\n\n return sandboxSlices;\n }\n\n disassociateSandboxAndSlice(sandbox, slice) {\n this.returnSandbox(sandbox);\n sandbox.slice = null;\n this.returnSlice(slice, 'EUNCAUGHT');\n }\n\n /**\n * This method will call this.startSandboxWork(sandbox, slice) for each element { sandbox, slice }\n * of the array returned by this.matchSlicesWithSandboxes(availableSandboxes) until all allocated sandboxes\n * are working. It is possible for a sandbox to interleave with calling distributeQueuedSlices and leave a sandbox\n * that is not working. Moreover, this.queuedSlices may be exhausted before all sandboxes are working.\n * @returns {Promise<void>}\n */\n async distributeQueuedSlices () {\n const numCores = this.unallocatedSpace;\n\n // If there's nothing there, or we're reentering, bail out.\n if (this.queuedSlices.length === 0 || numCores <= 0 || this.matching) {\n // Interesting and noisy.\n // debugging('supervisor') && console.log(`Supervisor.distributeQueuedSlices: Do not nest work, fetch or matching slices with sandboxes: queuedSlices ${this.queuedSlices.length}, matching ${this.matching}, fetching ${this.isFetchingNewWork}, numCores ${numCores}`);\n return Promise.resolve();\n }\n\n //\n // Use the pseudo-mutex to prevent uncontrolled interleaving with fetchTask,\n // matchSlicesWithSandboxes and distributeQueuedSlices\n let sandboxSlices;\n this.acquire(numCores);\n try {\n sandboxSlices = await this.matchSlicesWithSandboxes(numCores);\n } finally {\n this.release();\n }\n\n debugging('supervisor') && console.log(`distributeQueuedSlices: ${sandboxSlices.length} sandboxSlices ${this.dumpSandboxSlices(sandboxSlices)}, matching ${this.matching}, fetching ${this.isFetchingNewWork}`);\n\n for (let sandboxSlice of sandboxSlices) {\n\n const { sandbox, slice } = sandboxSlice;\n try {\n if (sandbox.isReadyForAssign) {\n try {\n let timeoutMs = Math.floor(Math.min(+Supervisor.lastAssignFailTimerMs || 0, 10 * 60 * 1000 /* 10m */));\n await a$sleepMs(timeoutMs);\n await this.assignJobToSandbox(sandbox, slice.jobAddress);\n } catch (e) {\n console.error(`Supervisor.distributeQueuedSlices: Could not assign slice ${slice.identifier} to sandbox ${sandbox.identifier}.`);\n if (Supervisor.debugBuild) console.error(`...exception`, e);\n Supervisor.lastAssignFailTimerMs = Supervisor.lastAssignFailTimerMs ? +Supervisor.lastAssignFailTimerMs * 1.25 : Math.random() * 200;\n this.disassociateSandboxAndSlice(sandbox, slice);\n continue;\n }\n }\n\n if (!Supervisor.lastAssignFailTimerMs)\n Supervisor.lastAssignFailTimerMs = Math.random() * 200;\n this.startSandboxWork(sandbox, slice);\n Supervisor.lastAssignFailTimerMs = false;\n\n } catch (e) {\n // We should never get here.\n console.error(`Supervisor.distributeQueuedSlices: Failed to execute slice ${slice.identifier} in sandbox ${sandbox.identifier}.`);\n if (Supervisor.debugBuild) console.error('...exception', e);\n this.disassociateSandboxAndSlice(sandbox, slice);\n }\n }\n }\n\n /**\n *\n * @param {Sandbox} sandbox\n * @param {opaqueId} jobAddress\n * @returns {Promise<void>}\n */\n assignJobToSandbox(sandbox, jobAddress) {\n // Any error will be caught in distributeQueuedSlices.\n return sandbox.assign(jobAddress);\n }\n\n /**\n * Handles reassigning or returning a slice that was rejected by a sandbox.\n * \n * The sandbox will be terminated by this.returnSandbox in finalizeSandboxAndSlice. In this case,\n * if the slice does not have a rejected property already, reassign the slice to a new sandbox\n * and add a rejected property to the slice to indicate it has already rejected once, then set slice = null\n * in the return SandboxSlice so that finalizeSandboxAndSlice won't return slice to scheduler.\n * \n * If the slice rejects with a reason, or has a rejected time stamp (ie. has been rejected once already)\n * then return the slice and all slices from the job to the scheduler and\n * terminate all sandboxes with that jobAddress.\n * @param {Sandbox} sandbox \n * @param {Slice} slice\n * @returns {Promise<SandboxSlice>}\n */\n async handleWorkReject(sandbox, slice, rejectReason) {\n if (!this.rejectedJobReasons[slice.jobAddress])\n this.rejectedJobReasons[slice.jobAddress] = [];\n\n this.rejectedJobReasons[slice.jobAddress].push(rejectReason); // memoize reasons\n\n // First time rejecting without a reason. Try assigning slice to a new sandbox.\n if (rejectReason === 'false' && !slice.rejected) {\n // Set rejected.\n slice.rejected = Date.now();\n // Schedule the slice for execution.\n this.scheduleSlice(slice, true /* placeInTheFrontOfTheQueue*/, false /* noDuplicateExecution*/);\n \n // Null out slice so this.returnSlice will not be called in finalizeSandboxAndSlice.\n // But we still want this.returnSandbox to terminate the sandbox.\n slice = null;\n } else { // Slice has a reason OR rejected without a reason already and got stamped.\n \n // Purge all slices and sandboxes associated with slice.jobAddress .\n this.purgeAllWork(slice.jobAddress);\n // Clear jobAddress from this.cache .\n this.cleanJobCache();\n\n // Add to array of rejected jobs.\n let rejectedJob = {\n address: slice.jobAddress,\n reasons: this.rejectedJobReasons[slice.jobAddress],\n }\n this.rejectedJobs.push(rejectedJob);\n\n // Tell everyone all about it, when allowed.\n if (dcpConfig.worker.allowConsoleAccess || Supervisor.debugBuild)\n {\n if (slice.rejected)\n console.warn(`Supervisor.handleWorkReject: The slice ${slice.identifier} was rejected twice.`);\n else\n console.warn(`Supervisor.handleWorkReject: The slice ${slice.identifier} was rejected with reason ${rejectReason}.`);\n console.warn(' All slices with the same jobAddress returned to the scheduler.');\n console.warn(' All sandboxes with the same jobAddress are terminated.');\n }\n //\n // this.purgeAllWork(jobAddress) terminates all sandboxes with jobAddress,\n // and it also returns to scheduler all slices with jobAddress.\n // Therefore null out slice and sandbox so finalizeSandboxAndSlice doesn't do anything.\n // \n sandbox = null;\n slice = null;\n }\n return { sandbox, slice };\n }\n\n /**\n * Schedule the slice to be executed.\n * If slice is already executing and noDuplicateExecution is true, return the slice with reason.\n * @param {Slice} slice\n * @param {boolean} [placeInTheFrontOfTheQueue=false]\n * @param {boolean} [noDuplicateExecution=true]\n * @param {string} [reason]\n */\n scheduleSlice(slice, placeInTheFrontOfTheQueue = false, noDuplicateExecution = true, reason) {\n // When noDuplicateExecution, if slice is already executing, do nothing.\n let workingSlices = [];\n if (noDuplicateExecution)\n workingSlices = this.allocatedSlices;\n\n if (!workingSlices.indexOf(slice)) {\n // Reset slice state to allow execution.\n slice.status = SLICE_STATUS_UNASSIGNED;\n slice.allocated = false;\n // Enqueue in the to-be-executed queue.\n if (placeInTheFrontOfTheQueue) this.queuedSlices.unshift(slice);\n else this.queuedSlices.push(slice);\n }\n }\n\n /**\n * Purge all slices and sandboxes with this jobAddress.\n * @param {address} jobAddress\n * @param {boolean} [onlyPurgeQueuedAndAllocated=false]\n */\n purgeAllWork(jobAddress, onlyPurgeQueuedAndAllocated = false) {\n // Purge all slices and sandboxes associated with jobAddress .\n const deadSandboxes = this.sandboxes.filter(sandbox => sandbox.jobAddress === jobAddress);\n\n if (deadSandboxes.length > 0) {\n debugging('supervisor') && console.log(`purgeAllWork(${this.dumpJobAddress(jobAddress)}): sandboxes purged ${deadSandboxes.map(s => s.id)}, # of sandboxes ${this.sandboxes.length}`);\n deadSandboxes.forEach(sandbox => this.returnSandbox(sandbox));\n }\n\n let deadSlices;\n if (onlyPurgeQueuedAndAllocated) {\n deadSlices = this.queuedSlices.filter(slice => slice.jobAddress === jobAddress);\n if (deadSlices.length > 0 || this.allocatedSlices.length > 0)\n debugging('supervisor') && console.log(`purgeAllWork(${this.dumpJobAddress(jobAddress)}): dead queuedSlices ${deadSlices.map(s => s.sliceNumber)}, dead allocatedSlices ${this.allocatedSlices.map(s => s.sliceNumber)}`);\n deadSlices.push(...this.allocatedSlices);\n } else {\n deadSlices = this.slices.filter(slice => slice.jobAddress === jobAddress);\n }\n\n if (deadSlices.length > 0) {\n debugging('supervisor') && console.log(`purgeAllWork(${this.dumpJobAddress(jobAddress)}): slices purged ${deadSlices.map(s => s.sliceNumber)}, # of sandboxes ${this.sandboxes.length}`);\n this.returnSlices(deadSlices);\n this.removeQueuedSlices(deadSlices);\n }\n debugging('supervisor') && console.log(`purgeAllWork(${this.dumpJobAddress(jobAddress)}): Finished: slices ${this.slices.length}, queuedSlices ${this.queuedSlices.length}, assigned ${this.assignedSandboxes.length}, readied ${this.readiedSandboxes.length}, # of sandboxes ${this.sandboxes.length}`);\n }\n\n /**\n * Gives a slice to a sandbox which begins working. Handles collecting\n * the slice result (complete/fail) from the sandbox and submitting the result to the scheduler.\n * It will also return the sandbox to @this.returnSandbox when completed so the sandbox can be re-assigned.\n *\n * @param {Sandbox} sandbox - the sandbox to give the slice\n * @param {Slice} slice - the slice to distribute\n * @returns {Promise<void>} Promise returned from sandbox.run\n */\n async startSandboxWork (sandbox, slice) {\n var startDelayMs, reason = 'unknown';\n\n try {\n slice.markAsWorking();\n } catch (e) {\n // This will occur when the same slice is distributed twice.\n // It is normal because two sandboxes could finish at the same time and be assigned the\n // same slice before the slice is marked as working.\n debugging() && console.debug('startSandboxWork: slice.markAsWorking exception:', e);\n return Promise.resolve();\n }\n\n // sandbox.requiresGPU = slice.requiresGPU;\n // if (sandbox.requiresGPU) {\n // this.GPUsAssigned++;\n // }\n\n if (Supervisor.startSandboxWork_beenCalled)\n startDelayMs = 1000 * (tuning.minSandboxStartDelay + (Math.random() * (tuning.maxSandboxStartDelay - tuning.minSandboxStartDelay)));\n else {\n startDelayMs = 1000 * tuning.minSandboxStartDelay;\n Supervisor.startSandboxWork_beenCalled = true;\n }\n\n try {\n debugging() && console.log(`startSandboxWork: Started ${this.dumpStatefulSandboxAndSlice(sandbox, slice)}, total sandbox count: ${this.sandboxes.length}, matching ${this.matching}, fetching ${this.isFetchingNewWork}`);\n if (Supervisor.sliceTiming) {\n slice['pairingDelta'] = Date.now() - slice['pairingDelta'];\n slice['executionDelta'] = Date.now();\n }\n let result;\n try {\n result = await sandbox.work(slice, startDelayMs);\n } finally {\n sandbox.allocated = false;\n slice.allocated = false;\n }\n if (Supervisor.sliceTiming) {\n slice['executionDelta'] = Date.now() - slice['executionDelta'];\n slice['resultDelta'] = Date.now();\n }\n slice.collectResult(result, true);\n // In watchdog, all sandboxes in working state, have their slice status sent to result submitter.\n // However, this can happen after the sandbox/slice has already sent results\n // to result submitter, in which case, the activeSlices table has already removed the row\n // corresponding to slice and hence is incapable of updating status.\n sandbox.changeWorkingToAssigned();\n this.assignedSandboxes.push(sandbox);\n debugging() && console.log(`startSandboxWork: Finished ${this.dumpStatefulSandboxAndSlice(sandbox, slice)}, total sandbox count: ${this.sandboxes.length}, matching ${this.matching}, fetching ${this.isFetchingNewWork}`);\n } catch(error) {\n let logLevel;\n\n if (error instanceof SandboxError) {\n logLevel = 'warn';\n // The message and stack properties of error objects are not enumerable,\n // so they have to be copied into a plain object this way\n const errorResult = Object.getOwnPropertyNames(error).reduce((o, p) => {\n o[p] = error[p]; return o;\n }, { message: 'Unexpected worker error' });\n slice.collectResult(errorResult, false);\n } else {\n logLevel = 'error';\n // This error was unrelated to the work being done, so just return the slice in the finally block.\n // For extra safety the sandbox is terminated.\n slice.result = null;\n slice.status = SLICE_STATUS_FAILED; /** XXXpfr @todo terminating sandbox? */\n }\n\n let errorString;\n switch (error.errorCode) {\n case 'ENOPROGRESS':\n reason = 'ENOPROGRESS';\n errorString = 'Supervisor.startSandboxWork - No progress error in sandbox.\\n';\n break;\n case 'ESLICETOOSLOW':\n reason = 'ESLICETOOSLOW';\n errorString = 'Supervisor.startSandboxWork - Slice too slow error in sandbox.\\n';\n break;\n case 'EUNCAUGHT':\n reason = 'EUNCAUGHT';\n errorString = `Supervisor.startSandboxWork - Uncaught error in sandbox ${error.message}.\\n`;\n break;\n case 'EFETCH':\n // reason = 'EFETCH'; The status.js processing cannot handle 'EFETCH'\n reason = 'unknown';\n errorString = `Supervisor.startSandboxWork - Could not fetch data: ${error.message}.\\n`;\n break;\n }\n \n const { getenv } = __webpack_require__(/*! ../../common/dcp-env */ \"./src/common/dcp-env.js\");\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 let workerConsole = sandbox.supervisorCache.cache.job[slice.jobAddress].workerConsole;\n const displayMaxInfo = Boolean(getenv('DCP_SUPERVISOR_DEBUG_DISPLAY_MAX_INFO')) || (workerConsole && dcpConfig.worker.allowConsoleAccess);\n\n const errorObject = {\n jobAddress: slice.jobAddress.substr(0,10),\n sliceNumber: slice.sliceNumber,\n sandbox: sandbox.id,\n jobName: sandbox.public ? sandbox.public.name : 'unnamed',\n };\n \n if (error.name === 'EWORKREJECT') {\n error.stack = 'Sandbox was terminated by work.reject()';\n const ss = await this.handleWorkReject(sandbox, slice, error.message);\n sandbox = ss.sandbox; slice = ss.slice;\n }\n\n if (!displayMaxInfo && error.errorCode === 'EUNCAUGHTERROR') {\n console[logLevel](`Supervisor.startSandboxWork - Uncaught error in sandbox, could not compute.\\n`, errorObject);\n } else if (!displayMaxInfo && error.errorCode === 'EFETCH_BAD_ORIGIN') {\n console[logLevel](`Supervisor.startSandboxWork - Could not fetch data: ${error.message}`);\n } else if (!displayMaxInfo && errorString) {\n console[logLevel](errorString, errorObject);\n } else if (!displayMaxInfo && error.name === 'EWORKREJECT') {\n console[logLevel](`Supervisor.startSandboxWork - Sandbox rejected work: ${error.message}`)\n } else {\n if (displayMaxInfo)\n errorObject.stack += '\\n --------------------\\n' + (error.stack.split('\\n').slice(1).join('\\n'));\n console[logLevel](`Supervisor.startSandboxWork - Sandbox failed: ${error.message}\\n`, errorObject);\n }\n } finally {\n await this.finalizeSandboxAndSlice(sandbox, slice, reason);\n }\n }\n\n /**\n * If slice && slice.result, then call await this.recordResult(slice) and this.returnSandbox(sandbox, slice) will have no effect.\n * If slice && !slice.result, then call this.returnSlice(slice, reason) and then this.returnSandbox(sandbox, slice) which terminates sandbox.\n * If !slice && sandbox, then terminate the sandbox with this.returnSandbox(sandbox, slice) .\n * If !slice && !sandbox, then do nothing.\n * @param {Sandbox} [sandbox]\n * @param {Slice} [slice]\n * @param {string} [reason]\n */\n async finalizeSandboxAndSlice(sandbox, slice, reason) {\n debugging('supervisor') && console.log(`finalizeSandboxAndSlice: sandbox ${sandbox ? sandbox.identifier : 'nade'}, slice ${slice ? slice.identifier : 'nade'}`);\n if (slice) {\n if (slice.result) await this.recordResult(slice);\n else this.returnSlice(slice, reason);\n }\n // It is possible that sandbox is already terminated\n // Because sandbox.allocated=false as soon as sandbox.work(...) completes.\n // But the await at or in finalizeSandboxAndSlice may allow pruneSandboxes to slither in.\n if (sandbox) this.returnSandbox(sandbox, slice, false /* verifySandboxIsNotTerminated*/);\n }\n\n /**\n * Terminates sandboxes and returns slices.\n * Sets the working flag to false, call @this.work to start working again.\n * \n * If forceTerminate is true: Terminates all sandboxes and returns all slices.\n * If forceTerminate is false: Terminates non-allocated sandboxes and returns queued slices.\n *\n * @param {boolean} [forceTerminate = true] - true if you want to stop the sandboxes from completing their current slice.\n * @returns {Promise<void>}\n */\n async stopWork (forceTerminate = true) {\n debugging('supervisor') && console.log('stopWork(${forceTerminate}): terminating sandboxes and returning slices to scheduler.');\n if (forceTerminate) {\n while (this.sandboxes.length) {\n this.returnSandbox(this.sandboxes[0], null, false);\n }\n\n await this.returnSlices(this.slices).then(() => {\n this.queuedSlices.length = 0;\n });\n } else {\n // Only terminate idle sandboxes and return only queued slices\n let idleSandboxes = this.sandboxes.filter(w => !w.allocated);\n for (const sandbox of idleSandboxes) {\n this.returnSandbox(sandbox, null, false /* verifySandboxIsNotTerminated*/);\n }\n\n await this.returnSlices(this.queuedSlices).then(() => {\n this.queuedSlices.length = 0;\n });\n\n await new Promise((resolve, reject) => {\n let sandboxesRemaining = this.allocatedSandboxes.length;\n if (sandboxesRemaining === 0)\n {\n resolve();\n }\n // Resolve and finish work once all sandboxes have finished submitting their results.\n this.on('submitFinished', () => {\n sandboxesRemaining--;\n if (sandboxesRemaining === 0)\n {\n console.log('All sandboxes empty, stopping worker and closing all connections');\n resolve();\n }\n });\n });\n }\n\n if (this.resultSubmitterConnection) {\n this.resultSubmitterConnection.off('close', this.openResultSubmitterConn);\n this.resultSubmitterConnection.close();\n this.resultSubmitterConnection = null;\n }\n\n if (this.taskDistributorConnection) {\n this.taskDistributorConnection.off('close', this.openTaskDistributorConn);\n this.taskDistributorConnection.close();\n this.taskDistributorConnection = null;\n }\n\n if (this.packageManagerConnection) {\n this.packageManagerConnection.off('close', this.openPackageManagerConn);\n this.packageManagerConnection.close();\n this.packageManagerConnection = null;\n }\n\n if (this.eventRouterConnection) {\n this.eventRouterConnection.off('close', this.openEventRouterConn);\n this.eventRouterConnection.close();\n this.eventRouterConnection = null;\n }\n\n this.emit('stop');\n }\n\n /**\n * Takes a slice and returns it to the scheduler to be redistributed.\n * Usually called when an exception is thrown by sandbox.work(slice, startDelayMs) .\n * Or when the supervisor tells it to forcibly stop working.\n *\n * @param {Slice} slice - The slice to return to the scheduler.\n * @param {string} [reason] - Optional reason for the return: 'ENOPROGRESS', 'EUNCAUGHT', 'ESLICETOOSLOW', 'unknown'.\n * @returns {Promise<*>} - Response from the scheduler.\n */\n returnSlice (slice, reason) {\n // When sliceNumber === 0 don't send a status message.\n if (slice.sliceNumber === 0) return Promise.resolve();\n \n debugging() && console.log(`Supervisor.returnSlice: Returning slice ${slice.identifier} with reason ${reason}.`);\n \n const payload = slice.getReturnMessagePayload(this.workerOpaqueId, reason);\n try\n {\n return this.resultSubmitterConnection.send('status', payload) /* resultSubmitterConnection can be null if worker is stopped */\n .then(response => {\n return response;\n }).catch(error => {\n debugging('supervisor') && console.error('Failed to return slice', {\n sliceNumber: slice.sliceNumber,\n jobAddress: slice.jobAddress,\n status: slice.status,\n error,\n }, 'Will try again on a new connection.');\n this.resultSubmitterMessageQueue.push({ operation: 'status', data: payload });\n this.resultSubmitterConnection.close();\n });\n }\n catch (error)\n {\n debugging('supervisor') && console.error(`Failed to return slice ${slice.identifier}, no connection to result submitter:`, error);\n }\n }\n\n /**\n * Bulk-return multiple slices, possibly for assorted jobs.\n * Returns slices to the scheduler to be redistributed.\n * Called in the sandbox terminate handler and purgeAllWork(jobAddress)\n * and stopWork(forceTerminate).\n *\n * @param {Slice[]} slices - The slices to return to the scheduler.\n * @returns {Promise<void>} - Response from the scheduler.\n */\n async returnSlices(slices) {\n if (!slices || !slices.length) return Promise.resolve();\n \n const slicePayload = [];\n slices.forEach(slice => { addToReturnSlicePayload(slicePayload, slice); });\n this.removeSlices(slices);\n\n debugging('supervisor') && console.log(`Supervisor.returnSlices: Returning slices ${this.dumpSlices(slices)}.`);\n\n return this.resultSubmitterConnection.send('status', {\n worker: this.workerOpaqueId,\n slices: slicePayload,\n }).then(response => {\n return response;\n }).catch(error => {\n const errorInfo = slices.map(slice => slice.identifier);\n debugging('supervisor') && console.error('Failed to return slice(s)', { errorInfo, error }, 'Will try again on new connection.');\n this.resultSubmitterMessageQueue.push({ operation: 'status', data: { worker: this.workerOpaqueId, slices: slicePayload } })\n this.resultSubmitterConnection.close();\n // Just in case the caller is expecing a DCP response\n return { success: false, payload: {} };\n });\n }\n\n /**\n * Submits the slice results to the scheduler, either to the\n * work submit or fail endpoints based on the slice status.\n * Then remove the slice from the @this.slices cache.\n *\n * @param {Slice} slice - The slice to submit.\n * @returns {Promise<void>}\n */\n async recordResult (slice) {\n // It is possible for slice.result to be undefined when there are upstream errors.\n if ( !(slice && slice.result))\n throw new Error(`recordResult: slice.result is undefined for slice ${slice.identifier}. This is ok when there are upstream errors.`);\n\n debugging('supervisor') && console.log(`supervisor: recording result for slice ${slice.identifier}.`);\n\n const jobAddress = slice.jobAddress;\n const sliceNumber = slice.sliceNumber;\n const authorizationMessage = slice.getAuthorizationMessage();\n\n /* @see result-submitter::result for full message details */\n const metrics = { GPUTime: 0, CPUTime: 0, CPUDensity: 0, GPUDensity: 0, total: 0 };\n const payloadData = {\n slice: sliceNumber,\n job: jobAddress,\n worker: this.workerOpaqueId,\n paymentAddress: this.paymentAddress,\n metrics,\n authorizationMessage,\n }\n\n const timeReport = slice.timeReport;\n if (timeReport && timeReport.total > 0) {\n metrics.GPUTime = timeReport.webGL;\n metrics.CPUTime = timeReport.CPU;\n metrics.CPUDensity = metrics.CPUTime / timeReport.total;\n metrics.GPUDensity = metrics.GPUTime / timeReport.total;\n metrics.total = timeReport.total;\n metrics.CPUTime = 1 + Math.floor(metrics.CPUTime);\n if (metrics.GPUTime > 0)\n metrics.GPUTime = 1 + Math.floor(metrics.GPUTime);\n }\n \n this.emit('submittingResult');\n\n if (!slice.isFinished)\n throw new Error('Cannot record result for slice that is not finished');\n\n if (slice.resultStorageType === 'pattern') /* This is a remote-storage slice. */\n payloadData.result = await this.sendResultToRemote(slice);\n else\n payloadData.result = encodeDataURI(slice.result.result); /* XXXwg - result.result is awful */\n debugging('supervisor') && console.log('Supervisor.recordResult: payloadData.result', payloadData.result.slice(0, 512));\n\n try {\n if (slice.completed) {\n\n /* work function returned a result */\n let resp = await this.resultSubmitterConnection.send(\n 'result',\n payloadData,\n )\n \n if (!resp.success) {\n if (resp.payload && resp.payload.code === 'DCPS-01002') { /* result submitter cannot connect to event router; try again */\n try {\n resp = await this.resendResult(payloadData)\n } catch (error) {\n debugging('supervisor') && console.error(`supervisor - failed to submit result for job ${jobAddress} after ${payloadData.sendRetries} attempts`)\n throw error;\n }\n }\n else\n throw new Error(`failed to submit result for slice ${slice.sliceNumber} of job ${jobAddress}`);\n }\n\n if (false) {}\n\n const receipt = {\n accepted: true,\n payment: resp.payload.slicePaymentAmount,\n };\n this.emit('submittedResult', resp.payload);\n this.emit('dccCredit', receipt);\n } else {\n /* slice did not complete for some reason */\n \n // If the slice from a job never completes and the job address exists in the ringBufferofJobs, \n // then we remove it to allow for another slice (from the same job) to be obtained by fetchTask\n this.ringBufferofJobs.buf = this.ringBufferofJobs.filter(element => element !== jobAddress);\n \n await this.returnSlice(slice);\n }\n } catch(error) {\n console.info(`1014: Failed to submit results for slice ${payloadData.slice} of job ${payloadData.job}`, error);\n this.emit('submitSliceFailed', error);\n } finally {\n this.emit('submitFinished');\n // Remove the slice from the slices array.\n this.removeSlice(slice);\n if (Supervisor.sliceTiming) {\n slice['resultDelta'] = Date.now() - slice['resultDelta'];\n console.log(`recordResult(${slice['pairingDelta']}, ${slice['executionDelta']}, ${slice['resultDelta']}): Completed slice ${slice.identifier}.`);\n } else\n debugging('supervisor') && console.log(`recordResult: Completed slice ${slice.identifier}.`);\n }\n }\n\n /**\n * Send a work function's result to a server that speaks our DCP Remote Data Server protocol.\n * E.g. https://gitlab.com/Distributed-Compute-Protocol/dcp-rds\n *\n * @param {Slice} slice - Slice object whose result we are sending.\n * @returns {Promise<string>}\n * @throws When HTTP status not in the 2xx range.\n */\n sendResultToRemote(slice)\n {\n /** XXXpfr @todo: Support file upload and other contentTypes. */\n function serializeContent(result, postParams) {\n const ctArray = postParams.contentType.split(';');\n switch (ctArray[0]) {\n case 'application/json':\n return JSON.stringify(result);\n case 'application/kvin':\n return (__webpack_require__(/*! kvin */ \"./node_modules/kvin/kvin.js\").serialize)(result);\n case 'application/octet-stream':\n case 'application/gzip':\n if (ctArray.length === 2 && ctArray[1] === 'base64')\n return btoa(result);\n // fall-through\n case 'text/plain':\n case 'text/html':\n case 'text/css':\n case 'text/csv':\n case 'text/javascript':\n return result;\n\n default:\n {\n const mtArray = ctArray[0].split('/');\n if (mtArray[0] === 'image' || mtArray[0] === 'video' || mtArray[0] === 'audio')\n return (ctArray.length === 2 && ctArray[1] === 'base64') ? btoa(result) : result;\n throw new Error(`sendResultToRemote: Unsupported contentType ${postParams.contentType}`);\n }\n }\n }\n\n // Construct postParams.\n const postParams = { ...slice.resultStorageParams };\n if (!postParams.contentType)\n postParams.contentType = 'application/json';\n if (!postParams.element)\n postParams.element = slice.sliceNumber;\n debugging('supervisor') && console.debug('sendResultToRemote postParams: ', postParams);\n\n // Construct result.\n const result = slice.result.result;\n if (result)\n postParams.content = serializeContent(result, postParams);\n else\n postParams.error = serializeContent(slice.error, postParams);\n debugging('supervisor') && console.debug('sendResultToRemote content: ', (result ? postParams.content : postParams.error).slice(0, 512));\n\n // Construct url.\n const sliceResultUri = makeValueURI('pattern', slice.resultStorageDetails, {\n slice: slice.sliceNumber,\n job: slice.jobAddress,\n });\n debugging() && console.debug('sendResultToRemote sliceResultUri: ', sliceResultUri);\n const url = new DcpURL(sliceResultUri);\n\n // Check allowed origins.\n if (this.makeSafeOriginList('sendResults').indexOf(url.origin) === -1)\n throw new Error(`Invalid origin for remote result storage: '${url.origin}'`);\n\n // Fetch.\n return justFetch(url, 'JSON', 'POST', false, postParams)\n .then((response) => encodeDataURI(JSON.stringify(response)));\n }\n}\n\n/**\n * Sandbox has had an error which is not from the work function: kill it\n * and try to redo the slice.\n */\nfunction handleSandboxError(supervisor, sandbox, error) {\n const slice = sandbox.slice;\n\n slice.sandboxErrorCount = (slice.sandboxErrorCount || 0) + 1;\n sandbox.slice = null;\n supervisor.returnSandbox(sandbox); /* terminate the sandbox */\n slice.status = SLICE_STATUS_UNASSIGNED; /* ToT */\n slice.allocated = false;\n console.warn(`Supervisor.handleSandboxError: Sandbox ${sandbox.identifier}...(${sandbox.public.name}/${slice.sandboxErrorCount}) with slice ${slice.identifier} had error.`, error);\n\n if (slice.sandboxErrorCount < dcpConfig.worker.maxSandboxErrorsPerSlice)\n supervisor.queuedSlices.push(slice);\n else {\n slice.error = error;\n supervisor.returnSlice(slice);\n }\n}\n\n/**\n * Add a slice to the slice payload being built. If a sliceList already exists for the\n * job-status-authMessage tuple, then the slice will be added to that, otherwise a new\n * sliceList will be added to the payload.\n *\n * @param {Object[]} slicePayload - Slice payload being built. Will be mutated in place.\n * @param {Slice} slice - The slice.\n * @param {String} status - Status update, eg. progress or scheduled.\n *\n * @returns {Object[]} mutated slicePayload array\n */\nfunction addToSlicePayload(slicePayload, slice, status) {\n // getAuthorizationMessage helps enforces the equivalence\n // !authorizationMessage <==> sliceNumber === 0\n const authorizationMessage = slice.getAuthorizationMessage();\n if (!authorizationMessage) return;\n\n // Try to find a sliceList in the payload which matches the job, status, and auth message\n let sliceList = slicePayload.find(desc => {\n return desc.job === slice.jobAddress\n && desc.status === status\n && desc.authorizationMessage === authorizationMessage;\n });\n\n // If we didn't find a sliceList, start a new one and add it to the payload\n if (!sliceList) {\n sliceList = {\n job: slice.jobAddress,\n sliceNumbers: [],\n status,\n authorizationMessage,\n };\n slicePayload.push(sliceList);\n }\n\n sliceList.sliceNumbers.push(slice.sliceNumber);\n\n return slicePayload;\n}\n\n/**\n * Add a slice to the returnSlice payload being built. If a sliceList already exists for the\n * job-isEstimation-authMessage-reason tuple, then the slice will be added to that, otherwise a new\n * sliceList will be added to the payload.\n *\n * @param {Object[]} slicePayload - Slice payload being built. Will be mutated in place.\n * @param {Slice} slice - The slice.\n * @param {String} [reason] - Optional reason to further characterize status; e.g. 'ENOPROGRESS', 'EUNCAUGHT', 'ESLICETOOSLOW', 'unknown'.\n *\n * @returns {Object[]} mutated slicePayload array\n */\nfunction addToReturnSlicePayload(slicePayload, slice, reason) {\n // getAuthorizationMessage helps enforces the equivalence\n // !authorizationMessage <==> sliceNumber === 0\n const authorizationMessage = slice.getAuthorizationMessage();\n if (!authorizationMessage) return;\n\n if (!reason) reason = slice.error ? 'EUNCAUGHT' : 'unknown';\n\n // Try to find a sliceList in the payload which matches the job, status, and auth message\n let sliceList = slicePayload.find(desc => {\n return desc.job === slice.jobAddress\n && desc.isEstimationSlice === slice.isEstimationSlice\n && desc.authorizationMessage === authorizationMessage\n && desc.reason === reason;\n });\n\n // If we didn't find a sliceList, start a new one and add it to the payload\n if (!sliceList) {\n sliceList = {\n job: slice.jobAddress,\n sliceNumbers: [],\n status: 'return',\n isEstimationSlice: slice.isEstimationSlice,\n authorizationMessage,\n reason,\n };\n slicePayload.push(sliceList);\n }\n\n sliceList.sliceNumbers.push(slice.sliceNumber);\n\n return slicePayload;\n}\n\n/**\n * Return DCPv4-specific connection options, composed of type-specific, URL-specific, \n * and worker-specific options, any/all of which can override the dcpConfig.dcp.connectOptions.\n * The order of precedence is the order of specificity.\n */\nfunction connectionOptions(url, label) {\n return leafMerge(/* ordered from most to least specific */\n dcpConfig.worker.dcp.connectionOptions.default,\n dcpConfig.worker.dcp.connectionOptions[label],\n dcpConfig.worker.dcp.connectionOptions[url.href]);\n}\n\n/** @type {number | boolean} */\nSupervisor.lastAssignFailTimerMs = false;\n/** @type {boolean} */\nSupervisor.startSandboxWork_beenCalled = false;\n/** @type {boolean} */\nSupervisor.debugBuild = ((__webpack_require__(/*! dcp/common/dcp-build */ \"./src/common/dcp-build.js\").build) === 'debug');\n/**\n * When Supervisor.sliceTiming is set to be true, it displays the timings of a every slice\n * slice['pairingDelta'] = timespan of when slice is paired with sandbox until execution starts\n * slice['executionDelta'] = timespan of execution in sandbox\n * slice['resultDelta'] = timespan of when sandbox finishes executing until recordResult completes.\n * @type {boolean}\n */\nSupervisor.sliceTiming = false;\n\nexports.Supervisor = Supervisor;\n\n\n//# sourceURL=webpack://dcp/./src/dcp-client/worker/supervisor.js?");
|
|
4445
4518
|
|
|
4446
4519
|
/***/ }),
|
|
4447
4520
|
|
|
@@ -4452,7 +4525,7 @@ eval("/* provided dependency */ var process = __webpack_require__(/*! ./node_mod
|
|
|
4452
4525
|
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
|
4453
4526
|
|
|
4454
4527
|
"use strict";
|
|
4455
|
-
eval("/* provided dependency */ var process = __webpack_require__(/*! ./node_modules/process/browser.js */ \"./node_modules/process/browser.js\");\n/**\n * @file dcp-client/worker/supervisor2/index.js\n * Code managing sandboxes, tasks, jobs, and slices within in a DCP Worker.\n * @author Wes Garland, wes@kingsds.network\n * Paul, paul@kingsds.network\n * @date Dec 2020\n * June 2022\n * @module supervisor\n */\n\n/* global dcpConfig */ // eslint-disable-line no-redeclare\n// @ts-check\n\n\nconst DCP_ENV = __webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\");\nconst { requireNative } = __webpack_require__(/*! dcp/dcp-client/webpack-native-bridge */ \"./src/dcp-client/webpack-native-bridge.js\");\nconst dcp4 = __webpack_require__(/*! dcp/protocol-v4 */ \"./src/protocol-v4/index.js\");\nconst debugging = (__webpack_require__(/*! dcp/debugging */ \"./src/debugging.js\").scope)('worker');\nconst constants = __webpack_require__(/*! dcp/common/scheduler-constants */ \"./src/common/scheduler-constants.js\");\nconst { setImmediate } = __webpack_require__(/*! dcp/common/dcp-timers */ \"./src/common/dcp-timers.js\");\nconst { EventEmitter } = __webpack_require__(/*! dcp/common/dcp-events */ \"./src/common/dcp-events/index.js\");\nconst { DCPError } = __webpack_require__(/*! dcp/common/dcp-error */ \"./src/common/dcp-error.js\");\nconst { Keystore, Address } = __webpack_require__(/*! dcp/dcp-client/wallet */ \"./src/dcp-client/wallet/index.js\");\nconst { assert } = __webpack_require__(/*! dcp/common/dcp-assert */ \"./src/common/dcp-assert.js\");\nconst { localStorage } = __webpack_require__(/*! dcp/common/dcp-localstorage */ \"./src/common/dcp-localstorage.js\");\nconst { DcpURL } = __webpack_require__(/*! dcp/common/dcp-url */ \"./src/common/dcp-url.js\");\nconst RingBuffer = __webpack_require__(/*! dcp/utils/ringBuffer */ \"./src/utils/ringBuffer.js\");\nconst { Synchronizer } = __webpack_require__(/*! dcp/common/concurrency */ \"./src/common/concurrency.js\");\nconst { JobManager } = __webpack_require__(/*! ./job-manager */ \"./src/dcp-client/worker/supervisor2/job-manager.js\");\nconst { Load } = __webpack_require__(/*! ./load */ \"./src/dcp-client/worker/supervisor2/load.js\");\nconst { Sandbox, SandboxError } = __webpack_require__(/*! ./sandbox2 */ \"./src/dcp-client/worker/supervisor2/sandbox2.js\");\nconst { sliceStatus } = __webpack_require__(/*! dcp/common/scheduler-constants */ \"./src/common/scheduler-constants.js\");\nconst hash = __webpack_require__(/*! dcp/common/hash */ \"./src/common/hash.js\");\nconst { calculateJoinHash } = __webpack_require__(/*! dcp/dcp-client/compute-groups */ \"./src/dcp-client/compute-groups/index.js\");\nconst { ModuleCache } = __webpack_require__(/*! ./module-cache */ \"./src/dcp-client/worker/supervisor2/module-cache.js\");\nconst { Inventory, leafMerge, a$sleepMs, ms, pct, generateOpaqueId, booley, compressJobMap, \n toJobMap, truncateAddress, encodeDataURI, makeValueURI, justFetch, stringify } = __webpack_require__(/*! dcp/utils */ \"./src/utils/index.js\");\n//const { CodeFlow } = require('client-oauth2');\n//const { text } = require('./lang').getLocaleStrings('en_CA'); /** XXXpfr @todo Don't know what to do with localization? */\n\n/** @typedef {import('dcp/dcp-client/wallet/eth').Address} Address */\n/** @typedef {import('dcp/protocol-v4/connection/connection').Connection} Connection */\n/** @typedef {string} opaqueId */ // 22 character base64 string\n/** @typedef {import('..').Worker} Worker */\n/** @typedef {import('..').SupervisorOptions} SupervisorOptions */\n/** @typedef {import('./slice2').Slice} Slice */\n/** @typedef {import('dcp/utils').SliceMessage} SliceMessage */\n\nconst addressTruncationLength = 20;\n\n//\n// Configs are moving around in dcpConfig and local worker configs, so set up some defaults.\nlet workerTuning = dcpConfig.worker;\nif (!workerTuning) workerTuning = dcpConfig.Supervisor;\nif (!workerTuning || !workerTuning.dcp || !workerTuning.dcp.tuning || !workerTuning.dcp.tuning.watchdogInterval\n || !workerTuning.sandbox || !workerTuning.allowOrigins || !workerTuning.minimumWage || !workerTuning.computeGroups)\n workerTuning = {\n dcp: {\n tuning: { watchdogInterval: 7, minSandboxStartDelay: 0.1, maxSandboxStartDelay: 0.7 },\n connectionOptions: { default: { identityUnlockTimeout: 15 * 60 /* seconds */ } },\n },\n sandbox: { progressReportInterval: 2 * 60 * 100 },\n allowOrigins: { fetchWorkFunctions: [], fetchArguments: [], fetchData: [], sendResults: [], any: [] },\n minimumWage: { CPU: 0, GPU: 0, 'in': 0, out: 0 },\n leavePublicGroup: false,\n pCores: 0,\n computeGroups: {},\n // The following configs are not in dcpConfig or worker configs (yet), but may be specified in local worker configs to override the defaults.\n pruneFrequency: 15 * 1000, // Maxiumum time interval where we check to prune used sandboxes.\n workerSandboxThreshold: 7, // When maxWorkingSandboxes >= workerSandboxThreshold, we allow an extra 25% of assigned sandboxes that won't be pruned.\n cachedJobsThreshold: 12, // Prune the unused job managers >= cachedJobsThreshold.\n};\n\n//\n// Flags for tracing.\n//\nconst selectiveEnable = false;\nconst displayWarnError = false || selectiveEnable;\nconst selectiveDebugging = selectiveEnable || debugging();\nconst debuggingError = false || selectiveDebugging || displayWarnError;\nconst debuggingWarn = false || selectiveDebugging || displayWarnError;\nconst selectiveDebugging2 = selectiveEnable && false || debugging('supervisor');\nconst displaySliceState = true;\nconst displayCompletedResults = false;\n\n/** \n * Adjust delay times when debugging.\n * The adjustment for niim is automatic, other debuggers must manually change this value.\n */\nlet timeDilation = 1;\nif (DCP_ENV.platform === 'nodejs')\n{\n /** Make timers 10x slower when running in niim */\n timeDilation = (requireNative('module')._cache.niim instanceof requireNative('module').Module) ? 10 : 1;\n}\n\n//\n// Index to functionality -- search for '_Idx' to toggle through the index.\n//\n// 1) Ctor: Supervisor constructor.\n// 2) Important property-like functions.\n// 3) Dtors: screenSaverDestroy, stopWork, purgeJob.\n// 4) Connection code.\n// 5) Work: Distribute slice to sandboxes.\n// 6) Return slices and sent progress reports to result-submitter-results.\n// 7) Task Distributor (TD): requestTask (Rq) support -- communication with TD.\n// 8) Aggregators from the job managers.\n// 9) Sandbox creation and management.\n// 10) Result-submitter-result support functions.\n// 11) Work reject.\n// 12) Unused functions that we need to review.\n//\n\n// _Idx\n//\n// Ctor: Supervisor constructor.\n//\n\n/** \n * Supervisor constructor\n * \n * A supervisor manages the communication with the scheduler, manages sandboxes, and\n * decides which workload should be sent to which sandboxes when.\n *\n * Start state:\n * - initial\n *\n * Intermediate states:\n * - ready\n * - stopping\n *\n * Terminal states:\n * - stopped\n *\n * Valid transitions:\n * - initial -> ready where that happens \n * - ready -> stopping\n * - stopping -> stopped\n *\n * @param {Worker} worker - The worker that created this instance.\n * @param {SupervisorOptions} options - Options for specifying custom behaviour and tuning,\n */\nfunction Supervisor(worker, options)\n{\n assert(options.identity instanceof Keystore);\n assert(options.paymentAddress instanceof Address);\n\n /**\n * Flag to indicate a debug build.\n * Used when we want to display extra information and do extra checks for developers only.\n * @type {boolean}\n */\n this.debugBuild = ((__webpack_require__(/*! dcp/common/dcp-build */ \"./src/common/dcp-build.js\").build) === 'debug');\n /**\n * When Supervisor.sliceTiming is set to be true, it displays the timings of a every slice\n * slice['queueingDelta'] = timespan of when slice is passed to jobManager.runQueuedSlice until sandbox.work\n * slice['executionDelta'] = timespan of execution in sandbox\n * slice['resultDelta'] = timespan of when sandbox finishes executing until recordResult completes.\n * @type {boolean}\n */\n this.sliceTiming = false;\n /** Used for analyzing the completed results in Supervisor.recordResult. */\n this.resultMap = {};\n\n /** @type {ModuleCache} */\n this.moduleCache = new ModuleCache(this);\n\n this.worker = worker;\n this.identity = options.identity;\n this.paymentAddress = options.paymentAddress;\n this.options = options;\n this.maxWorkingSandboxes = options.maxWorkingSandboxes || 1;\n this.maxTotalSandboxes = this.maxWorkingSandboxes;\n\n // We're making the assumption that if a worker has at least 7 sandboxes, \n // then the worker has sufficient resources to handle 25% more sandboxes in memory.\n // This assumption may be overridden by changing workerSandboxThreshold.\n if (this.maxWorkingSandboxes >= this.workerSandboxThreshold)\n this.maxTotalSandboxes = Math.ceil(1.25 * this.maxWorkingSandboxes);\n // When # of sandboxes reaches this level, we more aggressively prune.\n this.mustPruneSandboxLevel = Math.ceil(1.5 * this.maxTotalSandboxes);\n // Last prune time stamp.\n this.lastPrune = 0;\n // General time stamp.\n this.lastTime = 0;\n\n // Supervisor may get created by Worker where options.cores or options.targetLoad is not defined.\n this.numCPU = this.maxWorkingSandboxes;\n this.numGPU = 1;\n this.portionToUseCPU = pct(100);\n this.portionToUseGPU = pct(100);\n\n if (options.cores)\n {\n this.numCPU = options.cores.cpu || this.numCPU;\n this.numGPU = options.cores.gpu || this.numGPU;\n }\n if (options.targetLoad)\n {\n this.portionToUseCPU = options.targetLoad.cpu || pct(100);\n this.portionToUseGPU = options.targetLoad.gpu || pct(100);\n }\n\n this.tuning = {\n maxCPUAlloc: this.portionToUseCPU, /**< Maximum proportion of CPU time to attempt to use. */\n maxGPUAlloc: this.portionToUseGPU, /**< Maximum proportion of GPU time to attempt to use. */\n watchdogInterval: 7, /**< (seconds) How frequently to kick off an unsolicited requestTask. */\n prefetchInterval: 20, /**< (seconds) How many seconds into the future are looking to project capacity during work fetch. */\n minSandboxStartDelay: 0.1, /**< (seconds) Base minimum of this.delayMs, scaled by this.delayScaler. */\n maxSandboxStartDelay: 0.7, /**< (seconds) Base maximum random component of this.delayMs, scaled by this.delayScaler. */\n };\n this.tuning = leafMerge(this.tuning, workerTuning.dcp.tuning);\n if (options.watchdogInterval > 0)\n this.tuning.watchdogInterval = options.watchdogInterval; // Override.\n //this.tuning.watchdogInterval = 0.25;\n\n /**\n * Fine tune this.delayMs.\n * Note: Please discuss any change with Paul, paul@kingsds.network.\n * XXXpfr @todo Finalize the delay tuning.\n */\n this.delayScaler = 0.5;\n\n debugging('supervisor') && console.debug('Supervisor.tuning', this.tuning);\n\n /**\n * Note: targetLoad is not properly implemented yet.\n * XXXpfr @todo Collaborate with Wes to get it right.\n * @type {Load}\n */\n this.targetLoad = new Load({\n cpu: Math.min(this.maxWorkingSandboxes, this.numCPU),\n gpu: Math.min(this.maxWorkingSandboxes, this.numGPU)\n }).scale(this.tuning.maxCPUAlloc, this.tuning.maxGPUAlloc);\n\n /** @type {string[]} */\n this.allowedOrigins = workerTuning.allowOrigins.any;\n /** @type {string[]} */\n this.fetchWorkFunctions = workerTuning.allowOrigins.fetchWorkFunctions;\n /** @type {string[]} */\n this.fetchArguments = workerTuning.allowOrigins.fetchArguments;\n /** @type {string[]} */\n this.fetchData = workerTuning.allowOrigins.fetchData;\n /** @type {string[]} */\n this.sendResults = workerTuning.allowOrigins.sendResults;\n\n // In localExec, do not allow work function or arguments to come from the 'any' origins\n if (this.options.localExec)\n {\n this.allowedOrigins = this.allowedOrigins.concat(options.allowedOrigins.any);\n this.fetchWorkFunctions = this.fetchWorkFunctions.concat(options.allowedOrigins.fetchWorkFunctions);\n this.fetchArguments = this.fetchArguments.concat(options.allowedOrigins.fetchArguments);\n this.fetchData = this.fetchData.concat(options.allowedOrigins.fetchData);\n this.sendResults = this.sendResults.concat(options.allowedOrigins.sendResults)\n }\n\n if (options.allowedOrigins && options.allowedOrigins.length > 0)\n this.allowedOrigins = options.allowedOrigins.concat(this.allowedOrigins);\n \n //\n // The following 3 configs are not in dcpConfig or worker configs (yet), but may be specified in local worker configs to override the defaults.\n //\n /** @type {number} - Maxiumum time interval where we check to prune used sandboxes. */\n this.pruneFrequency = workerTuning.pruneFrequency || 15 * 1000;\n /** @type {number} - When maxWorkingSandboxes >= workerSandboxThreshold, we allow an extra 25% of assigned sandboxes that won't be pruned. */\n this.workerSandboxThreshold = workerTuning.workerSandboxThreshold || 7;\n /** @type {number} - Prune the unused job managers >= cachedJobsThreshold. */\n this.cachedJobsThreshold = workerTuning.cachedJobsThreshold || 12;\n\n /** @type {Object.<Address, JobManager>} */\n this.jobMap = {}; \n /** @type {Sandbox[]} - All sandboxes that are being used by the job managers. Makes sure we don't lose sandboxes. */\n this.sandboxInventory = [];\n /** @type {Sandbox[]} - Started sandboxes that are not in sandboxInventory yet. */\n this.readiedSandboxes = [];\n /** @type {JobManager[]} */\n this.jobManagerInventory = new Inventory('jobManagers');\n /** @type {Synchronizer} */\n this.state = new Synchronizer('initial', [ 'initial', 'ready', 'reconnecting', 'stopping', 'stopped', 'broken']);\n\n /** @type {string} */\n this.lastDcpsid = undefined;\n /** @type {Connection} */\n this.taskDistributor = null;\n /** @type {Connection} */\n this.resultSubmitter = null;\n /** @type {Connection} */\n this.eventRouter = null;\n /** @type {Connection} */\n this.packageManager = null;\n /** @type {Array<object>} */\n this.resultSubmitterMessageQueue = [];\n /** @type {Array<object>} */\n this.eventRouterMessageQueue = [];\n\n /** @type {object} */\n this.schedulerConfig = leafMerge(dcpConfig.scheduler, options.schedulerConfig);\n\n /** @type {opaqueId} */\n this.workerId = localStorage.getItem('workerId');\n if (!this.workerId || this.workerId.length !== constants.workerIdLength)\n {\n this.workerId = generateOpaqueId();\n localStorage.setItem('workerId', this.workerId);\n }\n /** @type {object[]} */\n this.rejectedJobs = [];\n /** \n * An N-slot ring buffer of job addresses. Stores all jobs that have had no more than 1 slice run in the ring buffer.\n * Required for the implementation of discrete jobs \n * @type {RingBuffer} \n */\n this.ringBufferofJobs = new RingBuffer(100); // N = 100 should be more than enough. \n /** @type {boolean} - pseudo-mutex guarding requestTask. */\n this.isFetchingNewWork = false;\n\n // Start up the connections.\n this.instantiateAllConnections();\n\n /**\n * Note: DCP-3241 asks to test Android to see if we need this restriction any longer.\n * XXXpfr @todo Hopefully we can delete this @hack.\n */\n // @hack - dcp-env.isBrowserPlatform is not set unless the platform is _explicitly_ set,\n // using the default detected platform doesn't set it.\n // Fixing that causes an error in the wallet module's startup on web platform, which I\n // probably can't fix in a reasonable time this morning.\n // ~ER2020-02-20\n if (!options.maxWorkingSandboxes\n && DCP_ENV.browserPlatformList.includes(DCP_ENV.platform)\n && navigator.hardwareConcurrency > 1) {\n this.maxWorkingSandboxes = navigator.hardwareConcurrency - 1;\n if (typeof navigator.userAgent === 'string') {\n if (/(Android).*(Chrome|Chromium)/.exec(navigator.userAgent)) {\n this.maxWorkingSandboxes = 1;\n this.emit('warning', 'Doing work with Chromimum browsers on Android is currently limited to one sandbox');\n }\n }\n }\n}\nexports.Supervisor = Supervisor;\nSupervisor.prototype = Object.getPrototypeOf(new EventEmitter('Supervisor')); // Fake out VSCode -- get's rid of a billion red-squigglies.\nSupervisor.prototype = new EventEmitter('Supervisor');\n/**\n * Preserve the constructor property.\n * @constructor\n */\nSupervisor.prototype.constructor = Supervisor;\n\n/**\n * Set up sandboxes and interval timers, then start to search for work.\n **/\nSupervisor.prototype.startWork = function Supervisor$startWork ()\n{\n /* Provide opportunity for calling code to hook ready/error events. */\n setImmediate(async () => {\n try\n {\n if (this.state.isNot('initial'))\n {\n if (this.state.setIf('stopped', 'initial')) {}\n else if (this.state.setIf('reconnecting', 'initial')) {}\n else if (this.state.setIf('broken', 'initial')) {}\n else if (this.state.is('ready')) return\n else throw new Error(`Supervisor startWork is in unexpected state ${this.state}, aborting...`);\n }\n this.instantiateAllConnections();\n\n await this.createSandboxes(this.maxWorkingSandboxes)\n .then(() => this.checkCapabilities());\n\n // Beacon interval timer.\n this.progressReportTimer = setInterval(() => this.emitProgressReport(), (workerTuning.sandbox.progressReportInterval || 2 * 60 * 100));\n // Watchdog: requestTask-driven interval timer.\n this.watchdogTimer = setInterval(() => this.requestTask() , ms(this.tuning.watchdogInterval));\n if (DCP_ENV.platform === 'nodejs' && this.options.localExec)\n {\n /* Interval timer helps keep worker alive forever, which we don't want in localExec. */\n this.progressReportTimer.unref();\n this.watchdogTimer.unref();\n }\n\n this.state.set('initial', 'ready');\n\n setImmediate(() => this.requestTask()); // Don't wait for watchdog.\n }\n catch(error)\n {\n this.state.set('initial', 'broken');\n this.emit('error', error);\n }\n });\n}\n\n/** Construct capabilities when necessary. */\nSupervisor.prototype.checkCapabilities = function Supervisor$checkCapabilities ()\n{\n if (!this.capabilities)\n {\n /**\n * Assign the capabilities of one the sandboxes before fetching slices from the scheduler.\n * @todo Remove this once fetchTask uses the capabilities of every sandbox to fetch slices.\n */\n const sandbox = this.readiedSandboxes.length > 0 ? this.readiedSandboxes[0] : this.sandboxInventory[0];\n if (sandbox)\n {\n this.capabilities = sandbox.capabilities;\n this.emit('capabilitiesCalculated', this.capabilities);\n }\n }\n\n if (DCP_ENV.isBrowserPlatform && this.capabilities.browser)\n this.capabilities.browser.chrome = DCP_ENV.isBrowserChrome;\n}\n\n// _Idx\n//\n// Important property-like functions.\n//\n\n/**\n * Universal delay milliseconds..\n * @returns {number}\n */\nSupervisor.prototype.delayMs = function Supervisor$delayMs (max = this.tuning.maxSandboxStartDelay, min = this.tuning.minSandboxStartDelay)\n{\n // Note: Please discuss any change with Paul, paul@kingsds.network.\n return 1000 * timeDilation * this.delayScaler * (min + Math.random() * (max - min));\n}\n\n/**\n * Indicates whether supervisor is ready for business.\n * @returns {boolean} - When true, the party is on...\n */\nSupervisor.prototype.isReady = function Supervisor$isReady()\n{\n return this.worker.working && this.state.is('ready');\n}\n\n/**\n * Safe access to Connection.close(...).\n * @param {Connection} connection\n * @param {string} [reason='requested']\n * @param {boolean} [immediate=false]\n * @returns {Promise<string>}\n */\nfunction safeClose(connection, reason = 'requested', immediate = false)\n{\n if (connection)\n {\n let msg;\n if (connection.state.is('closed')) msg = 'closed';\n else if (connection.state.is('closing')) msg = 'closing';\n else if (connection.state.is('close-wait')) msg = 'close-wait';\n if (msg)\n {\n debuggingWarn && console.warn(`${msg}, do not try to close again.`);\n return Promise.resolve(msg);\n }\n return connection.close(reason, immediate)\n .then(() => {\n return Promise.resolve(null);\n });\n }\n return Promise.resolve('already closed');\n}\n\n/**\n *\n * @param {string} operation\n * @param {*} data\n * @returns {Promise<string>}\n */\nSupervisor.prototype.saveForResubmitToRS = function Supervisor$saveForResubmitToRS(operation, data)\n{\n this.resultSubmitterMessageQueue.push({ operation, data });\n return safeClose(this.resultSubmitter);\n}\n\n/**\n * Error feedback to user.\n * @param {string} message\n * @param {*} extra\n */\nSupervisor.prototype.error = function Supervisor$error(message, extra)\n{\n const dcpError = new DCPError(message, extra);\n this.emit('error', dcpError);\n}\n\n/**\n * When true, the sandbox complete handler will look for another slice in the same job,\n * and if not found, then proceed to Supervisor.requestTask.\n * @returns {boolean}\n */\nSupervisor.prototype.runSliceFromSameJob = function Supervisor$runSliceFromSameJob()\n{\n //\n // Experimental, should be off by default.\n // Cf. sandbox complete handler in JobManager.hookUpSandboxListeners.\n //\n const disable = true;\n const tooManyJobs = this.activeJobCount() > this.maxWorkingSandboxes;\n return !disable && !tooManyJobs && this.unusedSandboxSlots() < 2;\n}\n\n/**\n * This function is used as the target number of sandboxes to be associated with slices and start working.\n * Warning: Do not rely on this information being 100% accurate -- it may change in the next instant.\n * @returns {number}\n */\nSupervisor.prototype.unusedSandboxSlots = function Supervisor$unusedSandboxSlots ()\n{\n return this.maxWorkingSandboxes - this.workingSliceCount();\n}\n\n/**\n * Create errorObj with error.code if it exists.\n * @param {Error} error\n * @returns {object}\n */\nSupervisor.prototype.checkCode = function Supervisor$checkCode (error)\n{\n if (!error) return '';\n const errorObj = { message: error.message };\n if (error['errorCode']) errorObj['errorCode'] = error['errorCode'];\n if (error['code']) errorObj['code'] = error['code'];\n return errorObj;\n}\n\n/**\n * Remove stack trace from error.\n * May not work when error is a string with no new-lines.\n * @param {string|Error} error\n * @returns {string|Error}\n */\nSupervisor.prototype.removeStackTrace = function Supervisor$removeStackTrace (error)\n{\n if (typeof error === 'string')\n {\n const errorLines = error.split('\\n');\n return (errorLines && errorLines.length > 0) ? errorLines[0] : error;\n }\n if (error instanceof Error)\n return error.message;\n return error;\n}\n\n// _Idx\n//\n// Dtors: screenSaverDestroy, stopWork, purgeJob.\n//\n\n/**\n * If we cannot create a new sandbox, that probably means we're on a screensaver worker\n * and the screensaver is down. So return the slices to the scheduler.\n */\nSupervisor.prototype.screenSaverDestroy = function Supervisor$screenSaverDestroy()\n{\n debugging('supervisor') && console.debug(`Supervisor.screenSaverDestroy: destroying all job managers and terminating all sandboxes.`);\n this.jobManagerInventory.forEach(jm => jm.destroy());\n this.jobManagerInventory = new Inventory('jobManagers');\n\n this.readiedSandboxes.forEach(sandbox => {\n if (!sandbox.isTerminated) sandbox.terminate(false);\n });\n this.readiedSandboxes = [];\n\n this.sandboxInventory.forEach(sandbox => {\n if (!sandbox.isTerminated) sandbox.terminate(false);\n });\n this.sandboxInventory = [];\n}\n\n/**\n * Terminates sandboxes and returns slices.\n * Sets the working flag to false, call @this.work to start working again.\n * \n * If forceTerminate is true: Terminates all sandboxes and returns all slices.\n * If forceTerminate is false: Terminates non-working sandboxes and returns initial and ready slices.\n *\n * @param {boolean} [forceTerminate = true] - true if you want to stop the sandboxes from completing their current slice.\n * @returns {Promise<void>}\n */\nSupervisor.prototype.stopWork = async function Supervisor$stopWork (forceTerminate = true)\n{\n selectiveDebugging && console.debug(`Supervisor.stopWork(${forceTerminate},${this.state}): terminating sandboxes and returning slices to scheduler.`);\n\n // Do a hard flush of the microtask queue and finish the current event loop.\n await new Promise((resolve) => setImmediate(() => setTimeout(resolve, 0)));\n\n if (this.state.setIf('ready', 'stopping')) {}\n else if (this.state.setIf('reconnecting', 'stopping')) {}\n else if (this.state.setIf('broken', 'stopping')) {}\n else if (this.state.is('stopped')) return\n else throw new Error(`Supervisor stopWork is in unexpected state ${this.state}, aborting...`);\n\n this.instantiateAllConnections();\n\n this.readiedSandboxes.forEach((sandbox) => {\n if (!sandbox.isTerminated) sandbox.terminate(false);\n });\n this.readiedSandboxes = [];\n\n if (forceTerminate)\n {\n for (const jm of this.jobManagerInventory)\n jm.destroy();\n\n this.sandboxInventory.forEach((sandbox) => {\n if (!sandbox.isTerminated) sandbox.terminate(false);\n });\n }\n else\n {\n let activeSliceCount = 0;\n const slicesToReturn = [];\n for (const jm of this.jobManagerInventory)\n {\n //jm.dumpSlices(`stopWork1:${jm.address}`);\n const queuedSlices = jm.queuedSlices;\n slicesToReturn.push(...queuedSlices);\n jm.removeSlices(queuedSlices);\n activeSliceCount += jm.activeSlices.length;\n jm.nonWorkingSandboxes.forEach((sandbox) => jm.returnSandbox(sandbox));\n }\n\n const reason = `Supervisor stopWork(${forceTerminate}): returning all non-finished slices that are not working.`;\n this.returnSlices(slicesToReturn, reason, false /*removeSlices*/);\n\n await new Promise((resolve) => {\n if (activeSliceCount === 0)\n resolve();\n // Resolve and finish work once all sandboxes have finished submitting their results.\n this.on('submitFinished', () => {\n if (--activeSliceCount === 0)\n {\n debuggingWarn && console.warn('All sandboxes empty, stopping worker and closing all connections');\n resolve();\n }\n });\n });\n\n for (const jm of this.jobManagerInventory)\n {\n //jm.dumpSlices(`stopWork2:${jm.address}`);\n jm.liveSandboxes.forEach((sandbox) => sandbox.terminate(false));\n jm._sandboxInventory = [];\n }\n }\n this.sandboxInventory = [];\n this.jobManagerInventory = new Inventory('jobManagers');\n\n this.closeConnections();\n\n this.emit('stop');\n this.state.set('stopping', 'stopped');\n}\n\n/**\n* Purge all traces of the job.\n* @param {JobManager} jobManager \n*/\nSupervisor.prototype.purgeJob = function Supervisor$purgeJob (jobManager)\n{\n selectiveDebugging && console.debug(`Supervisor.purgeJob ${jobManager.identifier}.`);\n this.jobManagerInventory.delete(jobManager);\n this.moduleCache.removeJob(jobManager.address);\n jobManager.destroy();\n}\n\n// _Idx\n//\n// Connection code.\n//\n\n/** \n * Connect the supervisor to a given scheduler sub-service. Reconnection \"DDoS\" from workers\n * mitigated via an exponential backoff algorithm.\n *\n * DCPv4 connections are lazily-initiated. Successful connection establishment detected by\n * observing the payload event, which normally triggers during versioning.\n */\nSupervisor.prototype.connectTo = function Supervisor$connectTo(which)\n{\n const config = (which === 'packageManager') ? dcpConfig.packageManager : this.schedulerConfig.services[which];\n const retryMinSleepMs = 100;\n const payloadResetCount = 3; /* How many payloadCount before we reset retryNextSleepMs. */\n \n var retryNextSleepMs = retryMinSleepMs;\n var payloadCount = 0;\n var options = leafMerge(/* ordered from most to least specific */\n workerTuning.dcp.connectionOptions.default,\n workerTuning.dcp.connectionOptions[which],\n workerTuning.dcp.connectionOptions[config.location.href],\n );\n\n /**\n * The payload event handler is an interesting way to handle exponential backoff\n * for the delay in this.reconnect.\n * XXXpfr @todo Revisit exponential backoff for this.reconnect in Sup2 Part II.\n */\n\n const handlePayloadEventFn = (ev) =>\n {\n if (++payloadCount === payloadResetCount)\n {\n this[which].removeEventListener('payload', handlePayloadEventFn);\n retryNextSleepMs = retryMinSleepMs; \n }\n }\n\n this[which] = new dcp4.Connection(config, this.identity, options);\n\n debugging() && console.debug(`Opening new connection ${this[which].id} to ${which}.`)\n\n const messageQueue = this[which + 'MessageQueue'];\n if (messageQueue && messageQueue.length > 0)\n resendRejectedMessages(this[which], messageQueue);\n\n this[which].on('payload', handlePayloadEventFn);\n\n this['reconnect' + which] = this.reconnect.bind(this, which);\n this[which].on('close', () => { this['reconnect' + which](); });\n}\n\n/**\n * Reconnect logic.\n * @param {string} which -- Name of the connection \n */\nSupervisor.prototype.reconnect = async function Supervisor$reconnect(which)\n{\n debugging('supervisor') && console.debug(`Supervisor.reconnect: ${which}:`, this.state.valueOf(), this[which].state.valueOf());\n const stateChange = this.state.testAndSet('ready', 'reconnecting');\n if (stateChange || this.state.is('reconnecting'))\n {\n await a$sleepMs(100); // Sleep for 100ms, maybe exp-backoff later if we need it.\n this.connectTo(which);\n if (stateChange) this.state.set('reconnecting', 'ready');\n debugging() && console.debug(`Supervisor.reconnect: Trying to reconnect ${which}`, this.state.valueOf(), this[which].state.valueOf());\n }\n}\n\n/**\n * Close a connection properly.\n * @param {string} which -- Name of the connection to close.\n */\nSupervisor.prototype.closeConnection = function Supervisor$closeConnection(which)\n{\n if (this[which])\n {\n this[which].off('close', this['reconnect' + which]);\n this[which].close();\n this[which] = null;\n }\n}\n\n/**\n * Close all connections.\n */\nSupervisor.prototype.closeConnections = function Supervisor$closeConnections()\n{\n /** XXXpfr @todo Remove when bug DCP-3094 is fixed. */\n a$sleepMs(500);\n\n this.closeConnection('taskDistributor');\n this.closeConnection('resultSubmitter');\n this.closeConnection('eventRouter');\n this.closeConnection('packageManager');\n}\n\n/**\n * Broadcast keepalive to all connections and optionally start up sandboxes.\n * @param {boolean} [createSandbox=false] - When true, creates sandboxes for unused sandbox slots.\n */\nSupervisor.prototype.keepalive = function Supervisor$keepalive(createSandbox = false)\n{\n return Promise.all([\n this.taskDistributor.keepalive(),\n this.resultSubmitter.keepalive(),\n this.eventRouter .keepalive(),\n this.packageManager .keepalive(),\n (createSandbox ? this.createSandboxes(this.maxWorkingSandboxes) : Promise.resolve()),\n ]);\n}\n\n/**\n * Open all connections. Used when supervisor is instantiated or stopped/started to initially open connections.\n */\nSupervisor.prototype.instantiateAllConnections = function Supervisor$instantiateAllConnections ()\n{\n if (!this.taskDistributor)\n this.connectTo('taskDistributor');\n if (!this.eventRouter)\n this.connectTo('eventRouter');\n if (!this.resultSubmitter)\n this.connectTo('resultSubmitter');\n if (!this.packageManager)\n this.connectTo('packageManager');\n}\n\n/**\n * Try sending messages that were rejected on an old instance of the given connection.\n * @param {Connection} connection\n * @param {Array<object>} messageQueue\n */\nasync function resendRejectedMessages (connection, messageQueue)\n{\n var message = messageQueue.shift();\n do \n {\n try\n {\n await connection.send(message.operation, message.data);\n }\n catch (error)\n {\n debuggingError && console.error(`Failed to resend message ${message.operation} to scheduler: ${error}. Will try again on a new connection.`);\n messageQueue.unshift(message);\n safeClose(connection);\n break;\n }\n message = messageQueue.shift();\n } while (message);\n}\n \n// _Idx\n//\n// Work: Distribute slice to sandboxes.\n//\n\n/** \n * UNUSED\n * @deprecated\n * Round-robin through the job managers, picking 1 slice to run each time.\n * Try to have the same number of working sandboxes for each job.\n * Try to run a slice on every available sandbox.\n */\nSupervisor.prototype.workOnCurrentTask = function Supervisor$workOnCurrentTask ()\n{\n return this.roundRobinSlices();\n}\n\n/**\n * This is for compatibility with Supervisor1 in sa worker.\n * When we get rid of Supervisor1 we can delete the ref in sa worker and then get rid of this function.\n */\nSupervisor.prototype.setDefaultIdentityKeystore = function Supervisor$setDefaultIdentityKeystore ()\n{\n}\n\n/**\n * Called in Worker.start().\n * Initial entry point after Worker constructor.\n * We need to start searching for work here to allow starting and stopping a worker.\n */\nSupervisor.prototype.work = function Supervisor$work ()\n{\n // Set up sandboxes and interval timers, then start to search for work.\n this.startWork();\n}\n\n/** \n * Round-robin through the job managers, picking 1 slice to run each time.\n * Try to have the same number of working sandboxes for each job.\n * Try to run a slice on every available sandbox.\n */\nSupervisor.prototype.roundRobinSlices2 = function Supervisor$roundRobinSlices2 ()\n{\n try\n {\n let first = true;\n const cursor = this.makeJobSelectionCursor(this.jobManagerInventory);\n while (true)\n {\n const slice = cursor.next();\n if (!slice) break; /* No more work can fit. */\n debugging('supervisor') && console.debug('roundRobinSlices: Executing slice', slice.identifier);\n slice.markAsReserved();\n slice.jobManager.runSlice(slice, first ? 0 : this.delayMs());\n first = false;\n }\n }\n finally\n {\n this.isFetchingNewWork = false;\n }\n}\n\n/**\n * We try to balance so that each job has the same number of working slices.\n *\n * NOTES:\n * 1) If count is such that it cannot be distributed evenly, we do not yet randomly assign the extras.\n * E.g. 3 jobs, j1, j2, j3: count = 5 -- so 2 jobs get extras -- the extras need to be assigned randomly.\n * @todo Assign the extras randomly.\n * 2) this.roundRobinSlices is not going to be what we use when sup2 is in final form.\n * We want to this.makeJobSelectionCursor and something like this.roundRobinSlices2\n *\n * In the outer loop,\n * when numworkingSandboxes=1, try to get a slice running for each job with 0 working sandboxes.\n * when numworkingSandboxes=2, try to get a slice running for each job with 1 working sandboxes.\n * when numworkingSandboxes=3, try to get a slice running for each job with 2 working sandboxes. Etc.\n * The idea is to balance the number of slices working on each job.\n * @param {number} [count=0] - The number of new slices to try to get running in sandboxes.\n */\nSupervisor.prototype.roundRobinSlices = function Supervisor$roundRobinSlices (count = 0)\n{\n try\n {\n if (!count) count = this.unusedSandboxSlots();\n if (!count || this.readySliceCount() < 1)\n return Promise.resolve();\n\n const slices = [];\n let numScheduled = 0\n let readyJobManagers = this.jobManagerInventory.filter(jm => jm.ready);\n let first = true;\n selectiveDebugging && console.debug('roundRobinSlices: START count', count, 'numJobMgrs', readyJobManagers.length, 'totalWorking(w/r/wo/wsbx/sbx)', this.workingSliceCount(), this.reservedSliceCount(), this.workingSliceOnlyCount(), this.workingSandboxCount(), this.sandboxCount());\n\n for (let numWorkingSandboxes = 1; numWorkingSandboxes <= this.maxWorkingSandboxes; numWorkingSandboxes++)\n {\n let sliceCount = 0;\n const beginNumScheduled = numScheduled;\n for (const jobMan of readyJobManagers)\n {\n const _readySlices = jobMan.readySlices;\n sliceCount += _readySlices.length\n const skip = numWorkingSandboxes <= jobMan.workingSlices.length;\n\n if (skip || _readySlices.length < 1)\n {\n // Noisy log message turned off by default.\n //debugging('supervisor') && console.debug('RRS0(numS, beginNumS, count, sliceCount, skip, _ready, numWorkingS(loop), workingSlices):', numScheduled, beginNumScheduled, count, sliceCount, skip, _readySlices.length, numWorkingSandboxes, jobMan.workingSlices.length);\n continue;\n }\n\n const slice = _readySlices[0];\n slices.push(slice);\n\n slice.markAsReserved();\n jobMan.runSlice(slice, first ? 0 : this.delayMs());\n\n first = false;\n if (++numScheduled >= count)\n break;\n }\n if (numScheduled >= count)\n {\n debugging('supervisor') && console.debug('RRS1(numS, beginNumS, count, sliceCount):', numScheduled, beginNumScheduled, count, sliceCount);\n break;\n }\n if (beginNumScheduled === numScheduled && sliceCount < 1)\n {\n debugging('supervisor') && console.debug('RRS2(numS, beginNumS, count, sliceCount):', numScheduled, beginNumScheduled, count, sliceCount);\n break;\n }\n }\n if (selectiveDebugging)\n {\n console.debug(`roundRobinSlices(working:(w/r/wo/wsbx/sbx)${this.workingSliceCount()},${this.reservedSliceCount()},${this.workingSliceOnlyCount()},${this.workingSandboxCount()},${this.sandboxCount()}): Started ${slices.length}/${numScheduled}/${count} scheduled slices`, compressSlices(slices));\n const sliceGrouper = {};\n slices.forEach((slice) => {\n const jm = slice.jobManager;\n if (!sliceGrouper[slice.jobAddress]) sliceGrouper[slice.jobAddress] = { cnt: 1, working: jm.workingSlices.length, queued: jm.queuedSlices.length, assigned: jm.assignedSandboxes.length, estimation: jm.isEstimation };\n else sliceGrouper[slice.jobAddress].cnt++;\n });\n console.debug(sliceGrouper);\n }\n if (selectiveDebugging2)\n {\n const jobGrouper = {};\n this.jobManagerInventory.forEach((jm) => {\n jobGrouper[jm.address] = { working: jm.workingSlices.length, queued: jm.queuedSlices.length, assigned: jm.assignedSandboxes.length, estimation: jm.isEstimation };\n });\n console.debug(jobGrouper);\n }\n }\n finally\n {\n this.isFetchingNewWork = false;\n }\n}\n\n/**\n * @callback cbNext\n * @returns {Slice}\n */\n\n/** \n * Factory function which instantiates a JobSelectionCursor. A JobSelectionCursor\n * steps the order that job slices should be selected for execution in the supervisor,\n * given the current state of the supervisor and the availability of jobs when the\n * inventory was snapshot. The entire slice scheduling algorithm is represented by\n * this cursor.\n *\n * The basic idea behind the scheduling of slices in this implementation is to keep as\n * many slices from different jobs running as possible, so as to reduce the likelihood\n * of resource contention between sandboxes.\n *\n * Slices are scheduled in here based on the following ruleset:\n * - pick a slice from the longest job that isn't running\n * - choose a slice from the remaining jobs, in order for shortest to longest slice time\n * - if there are any jobs which are nearly finished, every other slice comes from one\n * of these jobs, selected randomly????? <-- NYI. XXXpfr @todo Think about implementing...\n * - jobs which have slicePriority set by the task-distributor may be chosen in place\n * of slices in the above algorith. Jobs with a slicePriority closer to 1 are more likely\n * to exhibit this behaviour.\n * @param {JobManager[]} jobManagerInventory\n * @returns { { next: cbNext } }\n */\nSupervisor.prototype.makeJobSelectionCursor = function Supervisor$JobSelectionCursor (jobManagerInventory)\n{\n /* Variables in this scope function as state information for next() */\n var jobManIdx = 0;\n /** @type {JobManager[]} - All job managers that are ready that have at least one ready slice. */\n var jobManList;\n /** @type {JobManager[]} - All jobManList elements that correspond to preferred jobs. */\n var prefJobManList;\n /* Number of working sandboxes allowed for a given job. */\n var concurrency = 1;\n const that = this;\n \n function seed (_concurrency)\n {\n function countSandboxes(jobAddress)\n {\n const jobManager = that.jobMap[jobAddress];\n if (!jobManager) throw new Error(`Lost track of job manager for address '${jobAddress}'`);\n return jobManager.workingSlices.length;\n }\n \n // Reset.\n jobManIdx = 0;\n\n /* Populate jobManList with jobs which are ready and have at least one slice which is ready. */\n jobManList = jobManagerInventory.filter((jobMan) => jobMan.ready && jobMan.readySlices.length > 0);\n\n /* Populate jobManList with jobManagers whose # of working slices is less than _concurrency. */\n jobManList = jobManList.filter((jobMan) => countSandboxes(jobMan.address) < _concurrency);\n\n /* Increasing sort. */\n jobManList.sort((a,b) => a.estimateWallMs - b.estimateWallMs);\n\n /* Populate prefJobManList with jobs from jobManList which also have a slicePreference set. */\n prefJobManList = jobManList.filter(jobMan => jobMan.hasOwnProperty('slicePreference') );\n }\n\n /**\n * Takes slices off the ready list, marks it reserved and increments workingSandboxCoun,\n * because the slice will soon be working.\n * @param {JobManager} jobMan \n * @returns {Slice}\n */\n function reserveSlice (jobMan)\n {\n const _readySlices = jobMan.readySlices;\n if (_readySlices.length > 0)\n {\n const slice = _readySlices[0];\n slice.markAsReserved();\n return slice;\n }\n return null;\n }\n\n /**\n * Each invocation of next() identifies one slice to run, or returns false if none can run.\n * @returns {Slice}\n */\n function next ()\n {\n /* Adjust order to schedule the heaviest job's first slice asap. */\n jobManList.unshift(jobManList.pop());\n\n let workingSliceCount;\n do\n {\n seed(concurrency);\n\n /* Maybe schedule a prefered job slice based on random chance .*/\n if (prefJobManList.length > 0)\n {\n let prioRan = Math.random();\n let list = prefJobManList.filter(jm => jm['slicePreference'] >= prioRan);\n\n if (list.length > 0)\n {\n const jobMan = list[list.length * Math.random()];\n const slice = reserveSlice(jobMan);\n if (slice)\n return slice;\n }\n }\n\n /* Schedule a slice from next job; jobs are in increasing order of estimated run time. */\n while (jobManIdx < jobManList.length)\n {\n const jobMan = jobManList[jobManIdx];\n jobManIdx++;\n const slice = reserveSlice(jobMan);\n if (slice)\n return slice;\n }\n\n /* If this is reached, we did not schedule a slice with current seed. We need\n * to re-seed to look for newly-available work and sandboxes, ratcheting up the\n * concurrency (max # of each job running) until we find something we can do.\n */\n workingSliceCount = that.workingSliceCount();\n debugging() && console.debug(`job selection - no slice found from ${jobManList.length} jobs for concurrency=${concurrency} and ${workingSliceCount} working sandboxes`);\n } while (jobManList.length > 0 && workingSliceCount < that.maxWorkingSandboxes && concurrency++ < that.maxWorkingSandboxes);\n\n return false; /* Did not find any more work that fits. */\n }\n \n return { next };\n}\n\n/**\n * Handle sandbox.work(...) errors.\n * @param {Sandbox} sandbox \n * @param {Slice} slice \n * @param {Error} error \n * @return {Promise<string>}\n */\nSupervisor.prototype.handleSandboxWorkError = async function Supervisor$handleSandboxWorkError (sandbox, slice, error)\n{\n if (!slice.isWorking) // Sanity. Exception should never fire.\n throw new Error(`handleSandboxWorkError: slice ${slice.identifier} must be WORKING.`);\n\n let logLevel, reason;\n \n if (error instanceof SandboxError)\n {\n logLevel = 'warn';\n reason = error.errorCode;\n // The message and stack properties of error objects are not enumerable,\n // so they have to be copied into a plain object this way\n // @ts-ignore\n error = Object.getOwnPropertyNames(error).reduce((o, p) => {\n o[p] = error[p]; return o;\n }, { message: 'Unexpected worker error' });\n }\n else\n {\n logLevel = 'error';\n if (error)\n reason = `Slice ${slice.sliceNumber} in state ${slice.state} of job ${slice.jobAddress} failed to complete execution with error ${this.checkCode(error)}.`;\n else\n {\n reason = `Slice ${slice.sliceNumber} in state ${slice.state} of job ${slice.jobAddress} failed to complete execution.`;\n error = new Error(reason);\n }\n // This error was unrelated to the work being done, so just return the slice\n // in the promise.catch in JobManager.runSliceOnSandbox .\n assert(slice.result === null);\n }\n \n this.handleFailedSlice(slice, error);\n\n let errorString;\n switch (reason)\n {\n case 'ENOPROGRESS':\n errorString = 'No progress error in sandbox.\\n';\n break;\n case 'ESLICETOOSLOW':\n errorString = 'Slice too slow error in sandbox.\\n';\n break;\n case 'EUNCAUGHT':\n errorString = `Uncaught error in sandbox ${error.message}.\\n`;\n break;\n case 'EFETCH':\n // The status.js processing does not have a case for 'EFETCH' yet.\n errorString = `Could not fetch data: ${error.message}.\\n`;\n break;\n }\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 const displayMaxInfo = slice.jobManager.displayMaxDiagInfo;\n\n const errorObject = {\n jobAddress: truncateAddress(slice.jobAddress, addressTruncationLength),\n sliceNumber: slice.sliceNumber,\n sandbox: sandbox.id,\n jobName: sandbox.public ? sandbox.public.name : 'unnamed',\n };\n if (displayMaxInfo)\n errorObject.stack += '\\n --------------------\\n' + (error.stack.split('\\n').slice(1).join('\\n'));\n\n if (error.name === 'EWORKREJECT')\n {\n reason = 'EWORKREJECT'; // The status.js processing does not have a case for 'EWORKREJECT' yet.\n error.stack = 'Sandbox was terminated by work.reject()';\n await this.handleWorkReject(sandbox, slice, error.message);\n }\n else\n {\n this.returnSlice(slice, reason);\n slice.jobManager.returnSandbox(sandbox);\n }\n\n if (errorString)\n console[logLevel](errorString, errorObject);\n else if (error.name === 'EWORKREJECT')\n console[logLevel](`Slice rejected work: ${error.message}`)\n else\n console[logLevel](`Slice failed: ${error.message}\\n`, errorObject);\n\n return reason;\n}\n\n/**\n * Slice has thrown error during execution:\n * Mark slice as failed, compensate when job is dicrete, emit events.\n * @param {Slice} slice\n * @param {Error} error\n */\nSupervisor.prototype.handleFailedSlice = function Supervisor$handleFailedSlice (slice, error)\n{\n assert(error, 'error must be valid');\n slice.collectResult(error, false);\n\n // If the slice from a job never completes and the job address exists in the ringBufferofJobs,\n // then we remove it to allow for another slice (from the same job) to be obtained by fetchTask\n this.ringBufferofJobs.buf = this.ringBufferofJobs.filter(element => element !== slice.jobAddress);\n\n this.emit('submitSliceFailed', error);\n this.emit('submitFinished');\n}\n\n// _Idx\n//\n// Return slices and sent progress reports to result-submitter-results.\n// Return slices to result-submitter-status which marks the\n// corresponding row in activeSlices to be rescheduled on usually another worker.\n//\n\n/**\n * Bulk-return multiple slices, possibly for assorted jobs.\n * Returns slices to the scheduler to be redistributed.\n * Called in the sandbox terminate handler and purgeAllWork(jobAddress)\n * and stopWork(forceTerminate).\n *\n * @param {Slice[]} slices - The slices to return to the scheduler.\n * @param {string} [reason='unknown'] - Optional reason for the return: 'ENOPROGRESS', 'EUNCAUGHT', 'ESLICETOOSLOW', 'unknown'.\n * @param {boolean} [removeSlices=true] - When true, removes slices from this.sliceInventory .\n * @returns {Promise<*>} - Response from the scheduler.\n */\nSupervisor.prototype.returnSlices = function Supervisor$$returnSlices (slices, reason = 'unknown', removeSlices = true)\n{\n if (!slices || !slices.length) return Promise.resolve();\n debugging('supervisor') && console.debug('Supervisor.returnSlices: Returning slices', slices.map(slice => slice.identifier));\n\n const slicePayload = constructReturnSliceBuckets(slices, reason);\n if (removeSlices) slices.forEach((slice) => slice.jobManager.removeSlice(slice));\n\n try\n {\n return this.resultSubmitter.send('status', {\n worker: this.workerId,\n slices: slicePayload,\n }).catch(error => {\n const errorInfo = slices.map(slice => slice.identifier).sort();\n debuggingError && console.error('Failed to return slice(s)', { errorInfo, error }, 'Will try again on new connection.');\n return this.saveForResubmitToRS('status', { worker: this.workerId, slices: slicePayload });\n });\n }\n catch (error)\n {\n /* resultSubmitter can be null if worker is stopped */\n debuggingError && console.error(`Failed to return slices ${compressSlices(slices)}, no connection to result submitter:`, error);\n }\n}\n\n/** XXXpfr @todo TEMP -- Remove when sup2 replaces sup1 */\nfunction compressSlices(sliceArray)\n{\n const jobSliceMap = toJobMap(sliceArray, slice => slice.sliceNumber);\n return compressJobMap(jobSliceMap, false /* skipFirst*/, addressTruncationLength);\n}\n\n/**\n * Takes a slice and returns it to the scheduler to be redistributed.\n * Usually called when an exception is thrown by sandbox.work(...) .\n * Or when the supervisor tells it to forcibly stop working.\n *\n * @param {Slice} slice - The slice to return to the scheduler.\n * @param {string} [reason] - Optional reason for the return: 'ENOPROGRESS', 'EUNCAUGHT', 'ESLICETOOSLOW', 'unknown'.\n * @returns {Promise<*>} - Response from the scheduler.\n */\nSupervisor.prototype.returnSlice = function Supervisor$$returnSlice (slice, reason)\n{\n assert(slice.sliceNumber > 0 && slice.jobManager);\n debugging() && console.debug(`Supervisor.returnSlice: Returning slice ${slice.identifier} with reason ${reason}.`);\n\n if (!this.resultSubmitter)\n this.connectTo('resultSubmitter');\n\n try\n {\n slice.jobManager.removeSlice(slice);\n const payload = slice.getReturnMessagePayload(this.workerId, reason);\n return this.resultSubmitter.send('status', payload)\n .catch(error => {\n debuggingError && console.error('Failed to return slice', {\n sliceNumber: slice.sliceNumber,\n jobAddress: slice.jobAddress,\n status: slice.state.valueOf(),\n error,\n }, 'Will try again on a new connection.');\n return this.saveForResubmitToRS('status', payload);\n });\n }\n catch (error)\n {\n /* resultSubmitter can be null if worker is stopped */\n debuggingError && console.error(`Failed to return slice ${slice.identifier}, no connection to result submitter:`, error);\n }\n}\n\n/**\n * Send beacon to status.js for 'progress' and sliceStatus.scheduled.\n *\n * Run in an interval created in @constructor .\n * @returns {Promise<*>}\n */\nSupervisor.prototype.emitProgressReport = function emitProgressReport () \n{\n const slices = constructSliceBuckets( this.readySlices(), sliceStatus.scheduled );\n constructSliceBuckets( this.workingSlices(), 'progress', slices );\n\n debugging('supervisor') && console.debug('emitProgressReport:', stringify(slices));\n\n if (slices.length)\n {\n const progressReportPayload = {\n worker: this.workerId,\n slices,\n };\n\n try\n {\n return this.resultSubmitter.send('status', progressReportPayload)\n .catch(error => {\n debuggingError && console.error('479: Failed to send status beacon update:', error/*.message*/);\n return this.saveForResubmitToRS('status', progressReportPayload);\n });\n }\n catch (error) \n {\n /* resultSubmitter can be null if worker is stopped */\n debuggingError && console.error(`Failed to emit progress report, no connection to result submitter:`, error);\n }\n }\n}\n\n/**\n * Add a slice to the slice buckets being built. If a sliceBucket already exists for the\n * job-status-authMessage tuple, then the slice will be added to that, otherwise a new\n * sliceBucket will be added to the buckets.\n *\n * @param {Slice[]} slices - The slices.\n * @param {String} status - Status update, eg. progress or scheduled.\n * @param {Object[]} [sliceBuckets] - Slice buckets being built. Will be mutated in place.\n * @returns {Object[]} - mutated sliceBuckets array\n */\nfunction constructSliceBuckets (slices, status, sliceBuckets)\n{\n const jobMap = {};\n for (const slice of slices)\n {\n assert(slice.sliceNumber > 0 );\n if (!jobMap[slice.jobAddress]) jobMap[slice.jobAddress] = [];\n\n // Try to find a sliceBucket in the buckets which matches the job, status, and auth message.\n let sliceBucket = jobMap[slice.jobAddress].find(desc => {\n return desc.status === status\n && desc.authorizationMessage === slice.authorizationMessage;\n });\n\n if (!sliceBucket) jobMap[slice.jobAddress].push(slice.getMessage(status));\n else sliceBucket.sliceNumbers.push(slice.sliceNumber);\n }\n\n if (!sliceBuckets) return Object.values(jobMap);\n sliceBuckets.push(...Object.values(jobMap));\n return sliceBuckets;\n}\n \n/**\n * Add a slice to the returnSlice bucket being built. If a sliceBucket already exists for the\n * job-isEstimation-authMessage-reason tuple, then the slice will be added to that, otherwise a new\n * sliceBucket will be added to the buckets.\n *\n * @param {Slice[]} slices - The slices.\n * @param {String} [reason] - Optional reason to further characterize status; e.g. 'ENOPROGRESS', 'EUNCAUGHT', 'ESLICETOOSLOW', 'unknown'.\n * @param {Object[]} [sliceBuckets] - Optional slice buckets being built. Will be mutated in place.\n * @returns {Object[]} - mutated sliceBuckets array\n */\nfunction constructReturnSliceBuckets (slices, reason, sliceBuckets)\n{\n const jobMap = {};\n for (const slice of slices)\n {\n assert(slice.sliceNumber > 0 );\n if (!jobMap[slice.jobAddress]) jobMap[slice.jobAddress] = [];\n \n // Try to find a sliceBucket in the buckets which matches the job, estimation status, reason, and auth message.\n let sliceBucket = jobMap[slice.jobAddress].find(desc => {\n return desc.isEstimationSlice === slice.isEstimation\n && desc.authorizationMessage === slice.authorizationMessage\n && desc.reason === reason;\n });\n\n if (!sliceBucket) \n jobMap[slice.jobAddress].push(slice.getMessage('return', { isEstimationSlice: slice.isEstimation, reason }));\n else sliceBucket.sliceNumbers.push(slice.sliceNumber);\n }\n\n if (!sliceBuckets) return Object.values(jobMap);\n sliceBuckets.push(...Object.values(jobMap));\n return sliceBuckets;\n}\n \n// _Idx\n//\n// Task Distributor (TD): requestTask (Rq) support -- communication with TD.\n//\n\n/**\n * XXXpfr @todo Needs Work\n * For a given job, the scheduler stores an EMA approximation of average slice completion time in\n * jobPerfData.sliceCPUTime (and jobPerfData.sliceGPUTime, but we don't do the GPU analysis yet.)\n * However, each worker also tracks the same information and the ratio of local-info to scheduler-info\n * is returned by this.conversionQuantum so we can tell the task distributor how much work to return\n * from requestTask so that the work actually takes 5 minutes to complete when using all the worker sandboxes.\n * Note: \n * We average the completion times over the current jobs.\n * Define completion time in terms of sliceC(G)PUTime and sliceC(G)PUDensity\n * completion-time = (sliceCGPUTime + sliceCGPUTime) / ( sliceCPUDensity + sliceGPUDensity);\n * The local completion time is an EMA approximation of local completion-time as computed by Supervisor.recordResult.\n * The scheduler completion-time is computed directly from the corresponding row in jobPerfData.\n */\nSupervisor.prototype.conversionQuantum = function Supervisor$conversionQuantum()\n{\n let globalSpeed = 0, localSpeed = 0;\n for (const jobMan of this.jobManagerInventory)\n {\n const _globalTime = jobMan.globalTime;\n const _localTime = jobMan.statistics.ema;\n if (_globalTime > 0 && _localTime > 0)\n {\n //console.debug('conversionQuantum: local', _localTime, 'global', _globalTime);\n globalSpeed += _globalTime;\n localSpeed += _localTime;\n }\n }\n const conversion = globalSpeed > 0 ? localSpeed / globalSpeed : 1;\n return Math.min(Math.max(conversion, 0.2), 5.0); // Truncate if conversion is too bizarre.\n}\n\n/**\n * Remove all unreferenced jobs in this.jobManagerInventory and this.moduleCache.\n * Since job-managers are inserted into this.jobManagerInventory with a push, the job managers at the beginning are oldest.\n * Only delete #deleteCount of the oldest job-managers:\n * let deleteCount = this.jobManagerInventory.length - cachedJobsThreshold;\n * Edit cachedJobsThreshold to adjust the cache cleanup threshold.\n * @param {object[]} [newJobKeys=[]] - Jobs that should not be removed from this.jobManagerInventory and this.moduleCache.\n */\nSupervisor.prototype.clearUnusedJobManagersAndModuleCache = function Supervisor$clearUnusedJobManagersAndModuleCache(newJobKeys=[])\n{\n let deleteCount = this.jobManagerInventory.length - this.cachedJobsThreshold;\n if (deleteCount > 0)\n {\n selectiveDebugging && console.debug(`Supervisor.clearUnusedJobManagersAndModuleCache START: deleteCount ${deleteCount}/${this.jobManagerInventory.length}/${this.cachedJobsThreshold}.`);\n const jobMap = {};\n newJobKeys.forEach(jobAddress => { jobMap[jobAddress] = 1; });\n for (const jobManager of this.jobManagerInventory)\n {\n if (!jobMap[jobManager.address])\n {\n const sliceInventory = jobManager.sliceInventory.filter(slice => slice.isActive || slice.isQueued);\n if (sliceInventory.length < 1)\n {\n this.purgeJob(jobManager);\n if (--deleteCount < 1)\n break;\n }\n }\n }\n selectiveDebugging && console.debug(`Supervisor.clearUnusedJobManagersAndModuleCache FINISH: deleteCount ${deleteCount}/${this.jobManagerInventory.length}/${this.cachedJobsThreshold}.`);\n }\n}\n\n/**\n * Ask the scheduler (task distributor) for work.\n * @param {number} [unusedSandboxSlots]\n * @param {object[]} [jobs=[]] \n */\nSupervisor.prototype.requestTask = function Supervisor$requestTask (unusedSandboxSlots, jobs = [])\n{\n if (!this.isReady() || this.isFetchingNewWork)\n return Promise.resolve();\n\n if(!unusedSandboxSlots) unusedSandboxSlots = this.unusedSandboxSlots();\n if (unusedSandboxSlots < 1)\n {\n debugging('supervisor') && console.debug('requestTask: There are no unused sandbox slots.');\n return Promise.resolve();\n }\n\n // Refresh connections.\n this.instantiateAllConnections();\n\n // We prune for over this.maxTotalSandboxes about every 15 seconds, or when must prune level is reached.\n if (this.sandboxCount() > this.mustPruneSandboxLevel || Date.now() > this.lastPrune + this.pruneFrequency)\n {\n this.lastPrune = Date.now();\n this.pruneSandboxes();\n }\n\n try\n {\n this.isFetchingNewWork = true;\n const numCPUSlotToFill = this.numberOfAvailableSandboxSlots(unusedSandboxSlots);\n if (numCPUSlotToFill < 1)\n {\n //debugging() && console.debug('Predicted workload too high; not fetching additional work yet'); <-- Save Wes' msg...\n debugging() && console.debug('Supervisor.requestTask: We have enough, so start executing some slices.');\n return this.roundRobinSlices(); // roundRobinSlices guarantees this.isFetchingNewWork === false\n }\n\n /** XXXpfr @todo Get together with Wes to figure this out. */\n //let predictedLoad = this.predictLoad(Date.now() + ms(this.tuning.prefetchInterval)).load;\n\n const request = {\n numCores: numCPUSlotToFill,\n coreStats: this.getStatisticsCPU(),\n numGPUs: this.numGPU,\n //targetLoad: this.targetLoad.subtract(predictedLoad), /** XXXpfr @todo Get together with Wes to figure this out. */\n conversionQuantum: this.conversionQuantum(),\n capabilities: this.capabilities,\n paymentAddress: this.paymentAddress,\n jobAddresses: jobs.concat(this.options.jobAddresses || []), // When set, only fetches slices for these jobs.\n localExec: this.options.localExec,\n workerComputeGroups: this.generateWorkerComputeGroups(),\n minimumWage: workerTuning.minimumWage || this.options.minimumWage,\n loadedJobs: this.jobManagerInventory.map(jobMan => jobMan.address),\n readyJobs: this.jobManagerInventory.filter(jobMan => jobMan.ready).map(jobMan => jobMan.address),\n previouslyWorkedJobs: this.ringBufferofJobs.buf, // Only discrete jobs.\n rejectedJobs: this.rejectedJobs,\n };\n // Workers should be part of the public compute group by default.\n if (!booley(workerTuning.leavePublicGroup) && !booley(this.options.leavePublicGroup))\n request.workerComputeGroups.push(constants.computeGroups.public);\n\n // Call Task Distributor and handle response with this.addTaskToWorkload.\n this.fetchTask(request, (response) => this.addTaskToWorkload(request, response));\n }\n catch (error)\n {\n // Paranoid double-checking we don't accidently leave a live this.isFetchingNewWork.\n this.isFetchingNewWork = false;\n throw error;\n }\n}\n\n/** Gets the logical and physical number of cores and also the total number of sandboxes the worker is allowed to run. */\nSupervisor.prototype.getStatisticsCPU = function Supervisor$getStatisticsCPU ()\n{\n if (DCP_ENV.isBrowserPlatform)\n {\n return {\n worker: this.workerId,\n lCores: window.navigator.hardwareConcurrency,\n pCores: workerTuning.pCores || window.navigator.hardwareConcurrency,\n sandbox: this.maxWorkingSandboxes,\n }\n }\n\n return {\n worker: this.workerId,\n lCores: requireNative('os').cpus().length,\n pCores: requireNative('physical-cpu-count'),\n sandbox: this.maxWorkingSandboxes,\n }\n}\n\n/**\n * Callback for fetchTask.\n * @param {object} request \n * @param {object} response\n */\nSupervisor.prototype.addTaskToWorkload = function Supervisor$addTaskToWorkload (request, response)\n{\n try\n {\n const payload = response.payload;\n if (!response.success)\n {\n debugging() && console.debug('Task fetch failure; request=', request);\n debugging() && console.debug('Task fetch failure; response=', payload);\n // Only report errors when in 'ready' state.\n if (this.isReady()) throw new DCPError('Unable to fetch task for worker', payload);\n else return;\n }\n\n const sliceCount = payload.body.task.length || 0;\n if (sliceCount < 1)\n {\n if (selectiveDebugging2 && (this.lastTime + 7000 < Date.now()))\n {\n this.lastTime = Date.now();\n // Display the state of every slice.\n if (displaySliceState)\n {\n /** @type {JobManager} */\n const jm = this.jobManagerInventory.top();\n jm.dumpSlices(false /*details*/);\n }\n // Display completed results so far.\n if (displayCompletedResults && this.queuedSliceCount() < 1)\n {\n const values = Object.values(this.resultMap);\n if (values.length > 0)\n {\n values.forEach((descriptor) => descriptor.slices.sort((x,y) => x-y))\n console.debug(`Recorded results: job managers ${values.length}:`, this.resultMap);\n }\n }\n }\n this.emit('fetchedTask', { jobs: 0, slices: sliceCount });\n // There may be an extra slice to process.\n // roundRobinSlices guarantees this.isFetchingNewWork === false;\n return this.roundRobinSlices();\n }\n\n /**\n * payload structure: { owner: this.address, signature: signature, auth: messageLightWeight, body: messageBody };\n * messageLightWeight: { workerId: worker, jobSlices, schedulerId, jobCommissions }\n * messageBody: { newJobs: await-getNewJobsForTask(dbScheduler, task, request), task }\n */\n const { body, ...authorizationMessage } = payload;\n /** @type {{ newJobs: object, task: SliceMessage[] }} */\n const { newJobs, task } = body;\n assert(newJobs); // It should not be possible to have !newJobs -- we throw on !success.\n const newJobKeys = Object.keys(newJobs);\n const jobCount = newJobKeys.length;\n\n /*\n * Ensure all jobs received from the scheduler (task distributor) are:\n * 1. If we have specified specific jobs the worker may work on, the received jobs are in the specified job list\n * 2. If we are in localExec, at most 1 unique job type was received (since localExec workers are designated for only one job)\n * If the received jobs are not within these parameters, stop the worker since the scheduler cannot be trusted at that point.\n */\n if (request.jobAddresses.length > 0 && !newJobKeys.every((ele) => request.jobAddresses.includes(ele))\n || request.localExec && jobCount > 1)\n {\n this.error(\"Worker received slices it shouldn't have. Rejecting the work and stopping.\");\n process.exit(1);\n }\n\n selectiveDebugging && console.debug(`Supervisor.addTaskToWorkload: task: ${task.length}/${request.numCores}/${this.maxWorkingSandboxes}, conversion: ${request.conversionQuantum}, jobs: ${jobCount}, authSlices: ${compressJobMap(authorizationMessage.auth.authSlices, true /* skipFirst*/, addressTruncationLength /* digits*/)}`);\n\n // Clear out job managers w/o any queued slices,\n // and remove corresponding job references from module cache.\n // When a cached module no longer has any job references it is removed from the cache.\n this.clearUnusedJobManagersAndModuleCache(newJobKeys);\n\n // this.jobMap: job.address --> jobManager\n /** @type {Object.<Address, JobManager>} */\n this.jobMap = {};\n this.jobManagerInventory.forEach(jobManager => {\n this.jobMap[jobManager.address] = jobManager;\n });\n\n /** @type {Object.<Address, SliceMessage[]>} */\n const jobSliceMap = {};\n task.forEach((element) => {\n const address = String(element.jobAddress);\n if (!jobSliceMap[address]) jobSliceMap[address] = [element];\n else jobSliceMap[address].push(element);\n });\n\n debugging('supervisor') && console.debug('requestTask: slices, newJobs and jobMap', task.length, Object.keys(newJobs), Object.keys(this.jobMap));\n\n // Populate the job managers with slices, creating new job managers when necessary.\n // Set up discrete job ring buffer.\n for (const [jobAddress, jobEl] of Object.entries(newJobs))\n {\n if (this.jobMap.hasOwnProperty(jobAddress))\n {\n /** @type {JobManager} */\n const jm = this.jobMap[jobAddress];\n jm.update(jobEl, jobSliceMap[jobAddress], authorizationMessage);\n }\n else\n {\n // Add the slice messages to the job manager ctor, so that slice construction is after job manager is ready.\n const jobManager = new JobManager(this, jobEl, jobSliceMap[jobAddress], authorizationMessage);\n this.jobMap[jobAddress] = jobManager;\n this.jobManagerInventory.push(jobManager);\n\n // Populate the ring buffer based on job's discrete property.\n assert(jobEl.requirements);\n if (jobEl.requirements.discrete && this.ringBufferofJobs.find(address => address === jobEl.address) === undefined)\n this.ringBufferofJobs.push(jobEl.address);\n }\n }\n\n /**\n * The requestTask event fires when the supervisor has finished trying to\n * fetch work from the scheduler (task distributor). The data emitted is the\n * number of jobs and new slices to work on in the fetched task.\n *\n * @event Supervisor#requestTask\n * @type {object}\n */\n this.emit('fetchedTask', { jobs: jobCount, slices: sliceCount });\n\n // Start working on the slices.\n setImmediate(() => this.roundRobinSlices());\n }\n catch (error)\n {\n this.isFetchingNewWork = false; // Paranoid double checking that we don't leave this.isFetchingNewWork live.\n this.emit('fetchTaskFailed', error);\n debuggingError && console.error('Supervisor.requestTask failed!', error);\n }\n}\n\n/**\n * Returns the number of unused sandbox slots to fill -- sent to requestTask.\n * @returns {number}\n */\nSupervisor.prototype.numberOfAvailableSandboxSlots = function Supervisor$numberOfAvailableSandboxSlots(unusedSandboxSlots)\n{\n const _readySlices = this.readySlices();\n let numCores;\n if (this.options.priorityOnly && this.options.jobAddresses.length === 0)\n numCores = 0;\n else if (_readySlices.length > 1) // We have slices ready, no need to fetch.\n numCores = 0;\n else\n {\n // There are almost no ready slices (there may be 0 or 1), fetch a full task.\n // The task is full, in the sense that it will contain slices whose\n // aggregate execution time is this.maxWorkingSandboxes * 5-minutes.\n // However, there can only be unusedSandboxSlots # of long slices.\n // Thus we need to know whether the last slice in this.readySlices() is long or not.\n // (A long slice has estimated execution time >= 5-minutes on an average worker.)\n const longSliceCount = (_readySlices.length > 0 && _readySlices[0].isLong) ? 1 : 0;\n numCores = unusedSandboxSlots - longSliceCount;\n debugging('supervisor') && console.debug('numberOfAvailableSandboxSlots', numCores, unusedSandboxSlots, longSliceCount);\n }\n return numCores;\n}\n\n/**\n * @callback cbAddTaskToWorkload\n * @param {Response} response\n * @returns {Promise<void>}\n */\n\n/**\n * Call to fetch new slices from task distributor.\n * @param {*} request\n * @param {cbAddTaskToWorkload} addTaskToWorkload\n * @returns {Promise<void>}\n */\nSupervisor.prototype.fetchTask = async function Supervisor$fetchTask (request, addTaskToWorkload)\n{\n // Fetch a new task if we have insufficient slices queued, then start workers\n assert(this.isFetchingNewWork);\n\n this.instantiateAllConnections();\n\n // Top up sandboxes when necessary.\n const moreSandboxes = this.maxWorkingSandboxes - this.sandboxCount();\n if (moreSandboxes > 0)\n {\n await this.carefullyCreateSandboxes(moreSandboxes)\n .then(() => this.checkCapabilities()); /** XXXpfr @todo Do we need to check capabilities again? */\n }\n\n const fetchTimeout = setTimeout(() => {\n this.isFetchingNewWork = false;\n this.emit('warning', 'Fetch exceeded timeout, will reconnect at next watchdog interval');\n safeClose(this.taskDistributor, 'Fetch timed out', Math.random() > 0.5).catch(error => {\n this.error('Failed to close task-distributor connection', error);\n });\n safeClose(this.resultSubmitter, 'Fetch timed out', Math.random() > 0.5).catch(error => {\n this.error('Failed to close result-submitter connection', error);\n });\n this.instantiateAllConnections();\n }, 3 * 60 * 1000); // Max out at 3 minutes to fetch.\n\n // Ensure result submitter and task distributor connections before fetching tasks.\n try\n {\n await this.taskDistributor.keepalive();\n await this.resultSubmitter.keepalive();\n await this.taskDistributor.keepalive();\n }\n catch (e)\n {\n this.isFetchingNewWork = false;\n this.error('Failed to connect to result submitter, refusing to fetch slices. Will try again at next fetch cycle.', e);\n clearTimeout(fetchTimeout);\n safeClose(this.taskDistributor, 'Failed to connect to task-distributor', true).catch(error => {\n this.error('Failed to close task-distributor connection', error);\n });\n safeClose(this.resultSubmitter, 'Failed to connect to result-submitter', true).catch(error => {\n this.error('Failed to close result-submitter connection', error);\n });\n return Promise.resolve();\n }\n\n this.emit('fetchingTask');\n\n if (!this.taskDistributor) return\n return this.taskDistributor.send('requestTask', request)\n .then((response) => {\n addTaskToWorkload(response);\n })\n .catch((error) => {\n this.isFetchingNewWork = false; // Redundant.\n this.emit('fetchTaskFailed', error);\n this.error('Unable to request task from scheduler. Will try again on a new connection.', error);\n safeClose(this.taskDistributor, 'Failed to request task.', true);\n })\n .finally(() => {\n this.isFetchingNewWork = false;\n clearTimeout(fetchTimeout);\n });\n}\n\n/**\n * Generate the workerComputeGroups property of the requestTask message. \n * \n * Concatenate the compute groups object from dcpConfig with the list of compute groups\n * from the supervisor, and remove the public group if accidentally present. Finally,\n * we transform joinSecrets/joinHash into joinHashHash for secure transmission.\n *\n * @note computeGroup objects with joinSecrets are mutated to record their hashes. This\n * affects the supervisor options and dcpConfig. Re-adding a joinSecret property\n * to one of these will cause the hash to be recomputed.\n */\nSupervisor.prototype.generateWorkerComputeGroups = function Supervisor$generateWorkerComputeGroups ()\n{\n let computeGroups = Object.values(workerTuning.computeGroups || {});\n if (this.options.computeGroups)\n computeGroups = computeGroups.concat(this.options.computeGroups);\n computeGroups = computeGroups.filter(group => group.id !== constants.computeGroups.public.id);\n const hashedComputeGroups = [];\n for (const group of computeGroups)\n {\n const groupCopy = Object.assign({}, group);\n if ((group.joinSecret || group.joinHash) && (!group.joinHashHash || this.lastDcpsid !== this.taskDistributor.dcpsid))\n {\n let joinHash;\n if (group.joinHash)\n joinHash = group.joinHash.replace(/\\s+/g, ''); // strip whitespace\n else\n joinHash = calculateJoinHash(groupCopy);\n\n groupCopy.joinHashHash = hash.calculate(hash.eh1, joinHash, this.taskDistributor.dcpsid);\n delete groupCopy.joinSecret;\n delete groupCopy.joinHash;\n debugging('computeGroups') && console.debug(`Calculated joinHash=${joinHash} for`, groupCopy);\n }\n hashedComputeGroups.push(groupCopy);\n }\n this.lastDcpsid = this.taskDistributor.dcpsid;\n debugging('computeGroups') && console.debug('Requesting ', computeGroups.length, 'non-public groups for session', this.lastDcpsid);\n return hashedComputeGroups;\n}\n\n// _Idx\n//\n// Aggregators from the job managers.\n// Note: Not all functions are used yet.\n//\n/** XXXpfr @todo Figure out which aggregators to keep. */\n\n/**\n * Gather the count of job managers with queuedSlices.\n * @returns {number}\n */\nSupervisor.prototype.activeJobCount = function Supervisor$activeJobCount ()\n{\n let count = 0;\n this.jobManagerInventory.forEach((jobManager) => {\n if (jobManager.queuedSlices.length > 0) count++;\n });\n return count;\n}\n\n/**\n * Gather the ready slices from the job managers.\n * @returns {Slice[]}\n */\nSupervisor.prototype.readySlices = function Supervisor$readySlices ()\n{\n const readySlices = [];\n this.jobManagerInventory.forEach((jobManager) => {\n readySlices.push(...jobManager.readySlices);\n });\n return readySlices;\n}\n\n/**\n * Gather the working slices in the job managers.\n * @returns {Slice[]}\n */\nSupervisor.prototype.workingSlices = function Supervisor$workingSlices ()\n{\n const workingSlices = [];\n this.jobManagerInventory.forEach((jobManager) => {\n workingSlices.push(...jobManager.workingSlices);\n });\n return workingSlices;\n}\n\n/**\n * Gather the count of various kinds of slices over all the job managers.\n * @param {string} predicate - 'all;, 'ready', 'queued', 'reserved', 'working', 'workingOnly'.\n * @returns {number}\n */\nSupervisor.prototype.predicateSliceCount = function Supervisor$predicateSliceCount (predicate)\n{\n let count = 0;\n switch (predicate)\n {\n case 'all':\n this.jobManagerInventory.forEach((jobManager) => {\n count += jobManager.sliceInventory.length;\n });\n break\n case 'ready':\n this.jobManagerInventory.forEach((jobManager) => {\n count += jobManager.readySlices.length;\n });\n break;\n case 'queued':\n this.jobManagerInventory.forEach((jobManager) => {\n count += jobManager.queuedSlices.length;\n });\n break;\n case 'reserved':\n this.jobManagerInventory.forEach((jobManager) => {\n count += jobManager.reservedSlices.length;\n });\n break;\n case 'working': // both working and reserved (viz., soon-to-be-working)\n this.jobManagerInventory.forEach((jobManager) => {\n count += jobManager.workingSlices.length;\n });\n break;\n case 'workingOnly':\n this.jobManagerInventory.forEach((jobManager) => {\n count += jobManager.workingSlicesOnly.length;\n });\n break;\n }\n return count;\n}\n/** @returns {number} */\nSupervisor.prototype.sliceCount = function Supervisor$sliceCount () { return this.predicateSliceCount('all'); }\n/** @returns {number} */\nSupervisor.prototype.readySliceCount = function Supervisor$readySliceCount () { return this.predicateSliceCount('ready'); }\n/** @returns {number} */\nSupervisor.prototype.queuedSliceCount = function Supervisor$queuedSliceCount () { return this.predicateSliceCount('queued'); }\n/** @returns {number} */\nSupervisor.prototype.reservedSliceCount = function Supervisor$reservedSliceCount () { return this.predicateSliceCount('reserved'); }\n/** @returns {number} */\nSupervisor.prototype.workingSliceCount = function Supervisor$workingSliceCount () { return this.predicateSliceCount('working'); }\n/** @returns {number} */\nSupervisor.prototype.workingSliceOnlyCount = function Supervisor$workingSliceOnlyCount () { return this.predicateSliceCount('workingOnly'); }\n\n/**\n * Gather the count of working sandboxes over all the job managers.\n * @returns {number}\n */\nSupervisor.prototype.sandboxCount = function Supervisor$sandboxCount ()\n{\n return this.readiedSandboxes.length + this.sandboxInventory.filter((sandbox) => !sandbox.isTerminated).length;\n}\n\n/**\n * Gather the count of working sandboxes over all the job managers.\n * @returns {number}\n */\nSupervisor.prototype.workingSandboxCount = function Supervisor$workingSandboxCount ()\n{\n return this.sandboxInventory.filter((sandbox) => !sandbox.isTerminated && sandbox.isWorking).length;\n}\n\n// _Idx\n//\n// Sandbox creation and management.\n// \n\n/**\n * Create and start a Sandbox.\n * When this.readiedSandboxes.length > 0, use one of those sandboxes, instead of creating a new one.\n * @param {number} [delayMs=0] - The delay ms when calling sandbox.start(delayMs) .\n * @returns {Promise<Sandbox>}\n */\nSupervisor.prototype.createSandbox = function Supervisor$createSandbox (delayMs = 0)\n{\n const that = this;\n function getReadiedSandbox()\n {\n const sandbox = that.readiedSandboxes.pop();\n that.sandboxInventory.push(sandbox);\n return Promise.resolve(sandbox);\n }\n\n if (this.readiedSandboxes.length > 0)\n return getReadiedSandbox();\n\n // Do not place in this.readiedSandboxes, we'll directly use the return value of createSandbox.\n return this.createNewSandbox(delayMs, true/*putInInventory*/)\n .catch(() => {\n return this.carefullyCreateSandboxes(1)\n .then(() => {\n return getReadiedSandbox();\n });\n });\n}\n \n/**\n * Create and start a Sandbox.\n * Store it in this.readiedSandboxes or this.sandboxInventory according to putInInventory.\n * @param {number} [delayMs=0] - Millisecond delay when calling sandbox.start(delayMs), otherwise return it and use it.\n * @param {boolean} [putInInventory=false]\n * @returns {Promise<Sandbox>}\n */\nSupervisor.prototype.createNewSandbox = function Supervisor$createNewSandbox (delayMs = 0, putInInventory = false)\n{\n const rawSandbox = new Sandbox(this, { ...this.options.sandboxOptions });\n this.hookUpSandboxListeners(rawSandbox);\n return rawSandbox.start(delayMs)\n .then((sandbox) => {\n if (putInInventory) this.sandboxInventory.push(sandbox);\n else this.readiedSandboxes.push(sandbox);\n return sandbox;\n })\n .catch((error) => {\n if (!error) error = new Error('Unknown error creating sandbox.');\n debuggingWarn && console.warn(`Supervisor.createNewSandbox: Failed to start sandbox ${rawSandbox.identifier}.`, error);\n rawSandbox.terminate(false);\n if (error.code === 'ENOWORKER')\n throw new DCPError(\"Cannot use localExec without dcp-worker installed. Use the command 'npm install dcp-worker' to install the neccessary modules.\", 'ENOWORKER');\n throw error;\n });\n}\n\n/**\n * Bulk: create and start sandboxes and save in this.readiedSandboxes.\n * Call this function when there's a chance the evaluator is down.\n * @param {number} count - The number of sandboxes to create.\n * @returns {Promise<void>}\n */\nSupervisor.prototype.carefullyCreateSandboxes = async function Supervisor$carefullyCreateSandboxes (count)\n{\n if (count < 1) return;\n // If the evaluator cannot start (e.g. if the evalServer is not running),\n // then the while loop will keep retrying until the evalServer comes online.\n let retry = 0;\n while (true)\n {\n try\n {\n await this.createNewSandbox();\n if (count > 1)\n this.createSandboxes(count - 1);\n }\n catch (error)\n {\n if (error.code === 'ENOWORKER') throw error;\n // Now assume the evaluator is down and keep retrying.\n /** XXXpfr @todo Need better indicator that evaluator is down. */\n if ((retry++ % 6) === 0)\n this.error(`Failed to ready sandboxes; will keep retrying: ${this.checkCode(error)}`);\n await a$sleepMs(1000 * Math.max(5, retry));\n }\n }\n}\n\n/**\n * Bulk: create and start sandboxes and save in this.readiedSandboxes.\n * @param {number} count - The number of sandboxes to create.\n * @returns {Promise<void>}\n */\nSupervisor.prototype.createSandboxes = async function Supervisor$createSandboxes (count)\n{\n assert(count > 0);\n const promises = [], errors = [];\n for (let k = 0; k < count; k++)\n {\n promises.push(\n this.createNewSandbox(k === 0 ? 0: this.delayMs())\n .catch((error) => errors.push(this.checkCode(error))));\n }\n\n await Promise.all(promises);\n\n if (errors.length)\n this.emit('warning', `Failed to ready ${errors.length} of ${count} sandboxes: ${errors.map(err => err.message)}`);\n\n // Sort so that pop() will return sandboxes in increasing order.\n this.readiedSandboxes.sort((x,y) => y.id - x.id);\n\n debugging('supervisor') && console.debug(`createSandboxes: Created ${count-errors.length} sandboxes.`, this.readiedSandboxes.map(s => s.id));\n}\n\n/**\n * For a given sandbox, hook up all the Sandbox listeners.\n * @param {Sandbox} sandbox \n */\nSupervisor.prototype.hookUpSandboxListeners = function hookUpSandboxListeners (sandbox) \n{\n sandbox.addListener('ready', () => this.emit('sandboxReady', sandbox));\n\n sandbox.addListener('start', () => {\n this.emit('sandboxStart', sandbox);\n\n if (sandbox.slice)\n {\n try\n {\n const statusPayload = sandbox.slice.getMessagePayload(this.workerId, 'begin');\n return this.resultSubmitter.send('status', statusPayload).catch((error) => {\n debuggingError && console.error(`Error sending 'status' for slice ${sandbox.slice.identifier}:\\n\\t${error}\\n\\tWill try again on a new connection`);\n return this.saveForResubmitToRS('status', statusPayload);\n });\n } \n catch (error)\n {\n /* resultSubmitterConnection can be null if worker is stopped */\n debuggingError && console.error(`Failed to send 'begin' status for slice ${sandbox.slice.identifier}, no connection to result submitter`, error);\n }\n }\n });\n\n sandbox.addListener('workEmit', ({ eventName, payload }) => {\n // Need to check if the sandbox hasn't been assigned a slice yet.\n if (!sandbox.slice)\n this.error(`Sandbox not assigned a slice before sending workEmit message to scheduler.\\n\\t'workEmit' event originates from '${eventName}' event`);\n else\n {\n const slice = sandbox.slice;\n // Sometimes a sliceNumber===0 workEmit comes in before the client bundle is properly loaded.\n // Also happens with minor dcp-client version mismatches.\n // sliceNumber===0 <==> authorizationMessage undefined...\n if (!slice.authorizationMessage)\n this.emit('warning', `workEmit: missing authorization message for slice ${slice.identifier}`);\n else if (this.eventRouter) // No reason to emit if event router is closed.\n {\n const workEmitPayload = {\n eventName,\n payload,\n job: slice.jobAddress,\n slice: slice.sliceNumber,\n worker: this.workerId,\n authorizationMessage : slice.authorizationMessage,\n };\n\n const workEmitPromise = this.eventRouter.send('workEmit', workEmitPayload).catch(error => {\n debuggingWarn && console.warn(`workEmit: Unable to send ${eventName} for slice ${slice.identifier}: ${error.message}.\\n\\tTrying again on a new connection.`);\n this.eventRouterMessageQueue.push({ operation: 'workEmit', data: workEmitPayload })\n safeClose(this.eventRouter); // stopWork could slip-in during eventRouter.send\n if (this.debugBuild) this.error('workEmit error:', error);\n });\n\n if (this.debugBuild)\n {\n workEmitPromise.then(result => {\n if (!result) this.emit('warning', `workEmit: Event router did not accept event ${eventName}`);\n });\n }\n }\n }\n });\n\n sandbox.on('rejectedWorkMetrics', (data) => {\n // If the slice already has rejectedTimeReport, add this data to it. If not, assign this data to slices rejectedTimeReport property\n if (sandbox.slice) \n {\n if (!sandbox.slice.rejectedTimeReport) sandbox.slice.rejectedTimeReport = data.timeReport;\n else \n {\n ['total', 'CPU', 'webGL'].forEach((key) => {\n if (data.timeReport[key]) sandbox.slice.rejectedTimeReport[key] += data.timeReport[key];\n })\n }\n }\n });\n\n // If the sandbox terminated and we are not shutting down, then we should return all work which is\n // currently not being computed if all sandboxes are dead and the attempt to create a new one fails.\n sandbox.on('terminated', async () => {\n let nonTerminatedSandboxes = this.sandboxInventory.filter(sbx => !sbx.isTerminated);\n if (nonTerminatedSandboxes.length === 0 && this.worker.working)\n {\n debugging('supervisor') && console.debug(`hookUpSandboxListeners: Try to create 1 sandbox in the sandbox-terminated-handler.`);\n const _sandbox = await this.createNewSandbox()\n .catch((error) => {\n debugging('supervisor') && console.warn('Failed to replace terminated sandbox; evalserver may be gone.', error.message);\n error.message = 'Failed to replace terminated sandbox: ' + error.message;\n this.emit('warning', error);\n });\n\n // If we cannot create a new sandbox, that probably means we're on a screensaver worker\n // and the screensaver is down. So return the slices to the scheduler.\n if (!_sandbox) this.screenSaverDestroy();\n }\n });\n\n sandbox.on('error', (error) => this.emit('error', error));\n sandbox.on('warning', (warning) => this.emit('warning', warning));\n}\n\n/**\n * Terminate extra sandboxes over the limit: this.maxTotalSandboxes.\n * First terminate assigned sandboxes which are unlikely to be used with the current ready slices.\n * Then terminate the unassigned sandboxes: this.readiedSandboxes.\n * (There should be no readied sandboxes at this point.)\n * Then round-robin prune 1 assigned sandbox from each jobmanager.\n * XXXpfr @todo Prioritize sandboxes that we wish to keep.\n * E.g. When a sandbox is especially expensive to assign.\n */\nSupervisor.prototype.pruneSandboxes = function Supervisor$pruneSandboxes () \n{\n let pruneCount = this.sandboxCount() - this.maxTotalSandboxes;\n if (pruneCount <= 0) return;\n selectiveDebugging && console.debug(`Supervisor.pruneSandboxes START: pruneCount ${pruneCount}/${this.sandboxCount()}/${this.maxTotalSandboxes}.`);\n // Only prune the extras: jm.assignedSandboxes.length > jm.queuedSlices.length .\n // Round-robin prune 1 extra assigned sandbox from each jobmanager.\n const readyJobManagers = this.jobManagerInventory.filter(jm => jm.ready);\n while (true)\n {\n const _pruneCount = pruneCount;\n for (const jm of readyJobManagers)\n {\n if (jm.pruneExtraAssignedSandbox())\n {\n if (--pruneCount < 1)\n {\n selectiveDebugging && console.debug(`Supervisor.pruneSandboxes FINISH: unpruned ${pruneCount}/${this.sandboxCount()}/${this.maxTotalSandboxes}.`);\n return;\n }\n }\n }\n if (pruneCount === _pruneCount)\n break;\n }\n assert(pruneCount > 0);\n // Prune the excess non-assigned sandboxes -- we should never hit this.\n if (this.readiedSandboxes.length > 0)\n {\n const toPrune = this.readiedSandboxes.slice(0, pruneCount);\n this.readiedSandboxes = this.readiedSandboxes.slice(pruneCount);\n toPrune.forEach(sandbox => sandbox.terminate(false));\n pruneCount -= toPrune.length;\n if (pruneCount < 1)\n {\n selectiveDebugging && console.debug(`Supervisor.pruneSandboxes FINISH: unpruned ${pruneCount}/${this.sandboxCount()}/${this.maxTotalSandboxes}.`);\n return;\n }\n }\n // Round-robin prune 1 assigned sandbox from each jobmanager.\n while (true)\n {\n const _pruneCount = pruneCount;\n for (const jm of readyJobManagers)\n {\n if (jm.pruneAssignedSandbox())\n {\n if (--pruneCount < 1)\n {\n selectiveDebugging && console.debug(`Supervisor.pruneSandboxes FINISH: unpruned ${pruneCount}/${this.sandboxCount()}/${this.maxTotalSandboxes}.`);\n return;\n }\n }\n }\n if (pruneCount === _pruneCount)\n break;\n }\n this.sandboxInventory = this.sandboxInventory.filter((sandbox) => !sandbox.isTerminated);\n selectiveDebugging && console.debug(`Supervisor.pruneSandboxes FINISH: unpruned ${pruneCount}/${this.sandboxCount()}/${this.maxTotalSandboxes}.`);\n}\n\n// _Idx\n//\n// Result-submitter-result support functions.\n// Send in the results!!!\n//\n\n/**\n * Submits the slice results to the result-submitter service.\n * Then remove the slice from the its job manager.\n *\n * @param {Slice} slice - The slice to submit.\n * @returns {Promise<void>}\n */\nSupervisor.prototype.recordResult = function Supervisor$recordResult (slice)\n{\n // It is possible for slice.result to be undefined when there are upstream errors.\n if ( !(slice && slice.result))\n throw new Error(`recordResult: slice.result is undefined for slice ${slice.identifier}. This is ok when there are upstream errors.`); \n if (!slice.isComplete)\n throw new Error('Cannot record result for slice that has not completed execution successfully.');\n\n debugging('supervisor') && console.debug(`supervisor: recording result for slice ${slice.identifier}.`);\n\n /* @see result-submitter::result for full message details */\n const metrics = { GPUTime: 0, CPUTime: 0, CPUDensity: 0, GPUDensity: 0, total: 0 };\n const payloadData = {\n slice: slice.sliceNumber,\n job: slice.jobAddress,\n worker: this.workerId,\n paymentAddress: this.paymentAddress,\n metrics,\n authorizationMessage: slice.authorizationMessage,\n }\n\n const timeReport = slice.timeReport;\n if (timeReport)\n {\n debugging('supervisor') && console.debug('recordResult timeReport', timeReport);\n // If slice takes less than 1ms to execute, CPUTime will be 0, so compensate.\n if (timeReport.CPU < 1)\n {\n timeReport.CPU++;\n timeReport.total++;\n }\n if (timeReport.total < timeReport.CPU + timeReport.webGL)\n {\n // Compensate or throw? For now we compensate.\n debuggingWarn && console.warn(`Supervisor.recordResult:: Inconsistent time report -- total < CPU + webGL -- ${stringify(timeReport)}`)\n //throw new Error(`recordResult: Inconsistent time report -- total < CPU + webGL -- ${stringify(timeReport)}`)\n timeReport.total = timeReport.CPU + timeReport.webGL;\n }\n if (timeReport.total > 0)\n {\n slice.jobManager.updateStatistics(timeReport);\n metrics.total = timeReport.total;\n metrics.CPUTime = timeReport.CPU;\n metrics.GPUTime = timeReport.webGL;\n metrics.CPUDensity = metrics.CPUTime / timeReport.total;\n metrics.GPUDensity = metrics.GPUTime / timeReport.total;\n }\n }\n\n this.emit('submittingResult');\n\n if (!this.resultSubmitter)\n this.connectTo('resultSubmitter');\n\n if (slice.resultStorageType === 'pattern')\n return this.sendResultToRemote(slice)\n .then((response) => {\n payloadData.result = response;\n this.sendToResultSubmitter(slice, payloadData);\n });\n\n payloadData.result = encodeDataURI(slice.result.result);\n return this.sendToResultSubmitter(slice, payloadData);\n}\n\n/**\n * @param {Slice} slice\n * @param {*} payloadData\n * @returns {Promise<void>}\n */\nSupervisor.prototype.sendToResultSubmitter = function Supervisor$sendToResultSubmitter (slice, payloadData)\n{\n const that = this;\n function handleRSError (error, payloadData)\n {\n that.error(`Failed to submit results to scheduler for slice ${payloadData.slice} of job ${payloadData.job}`, error);\n //slice.jobManager.dumpSlices('recordResult');\n that.saveForResubmitToRS('result', payloadData)\n .then((msg) => {\n if (!error && msg) error = new Error(`resultSubmitter is ${msg}`);\n that.emit('submitSliceFailed', error);\n throw error;\n });\n }\n\n try\n {\n debugging('supervisor') && console.debug('Supervisor.recordResult: payloadData', payloadData.result.slice(0, 256), slice.identifier);\n\n return this.resultSubmitter.send('result', payloadData)\n .then((resp) => {\n if (!resp.success)\n throw resp.payload;\n\n debugging('supervisor') && console.debug('recordResult: SUCCESS', slice.identifier);\n\n const receipt = {\n accepted: true,\n payment: resp.payload.slicePaymentAmount,\n };\n this.emit('submittedResult', resp.payload);\n this.emit('dccCredit', receipt);\n })\n .catch ((error) => {\n handleRSError (error, payloadData);\n });\n }\n catch (error)\n {\n handleRSError (error, payloadData);\n }\n finally\n {\n slice.markAsFinished();\n this.emit('submitFinished');\n // Remove the slice from the job manager.\n slice.jobManager.removeSlice(slice);\n if (this.sliceTiming)\n {\n slice['resultDelta'] = Date.now() - slice['resultDelta'];\n console.debug(`recordResult(${slice['queueingDelta']}, ${slice['executionDelta']}, ${slice['resultDelta']}): Completed slice ${slice.identifier}.`);\n }\n if (selectiveDebugging)\n {\n if (!this.resultMap[slice.jobAddress]) this.resultMap[slice.jobAddress] = { slices: [], totalTimes: [] };\n this.resultMap[slice.jobAddress].slices.push(slice.sliceNumber);\n this.resultMap[slice.jobAddress].totalTimes.push(payloadData.metrics.total);\n }\n }\n}\n\n/**\n * Send a work function's result to a server that speaks our DCP Remote Data Server protocol.\n * @param {Slice} slice - Slice object whose result we are sending.\n * @returns {Promise<string>}\n * @throws When HTTP status not in the 2xx range.\n */\nSupervisor.prototype.sendResultToRemote = function Supervisor$sendResultToRemote (slice)\n{ \n // Construct postParams.\n const postParams = { ...slice.resultStorageParams };\n postParams.element = slice.sliceNumber;\n /** Currently data will be outputed as a JSON object, XXXpfr @todo: Support file upload and other contentTypes. */\n postParams.contentType = 'application/json';\n\n const result = slice.result.result;\n if (result) postParams.content = JSON.stringify(result);\n else postParams.error = JSON.stringify(slice.error);\n debugging('supervisor') && console.debug('sendResultToRemote: content: ', (result ? postParams.content : postParams.error).slice(0, 512));\n\n // Construct url.\n const sliceResultUri = makeValueURI('pattern', slice.resultStorageDetails, {\n slice: slice.sliceNumber,\n job: slice.jobAddress,\n });\n debugging() && console.debug('sendResultToRemote sliceResultUri: ', sliceResultUri);\n const url = new DcpURL(sliceResultUri);\n\n // Check allowed origins.\n if (this.allowedOrigins.indexOf(url.origin) === -1 && this.sendResults.indexOf(url.origin) === -1)\n throw new Error(`Invalid origin for remote result storage: '${url.origin}'`);\n\n return justFetch(url, 'JSON', 'POST', false, postParams)\n .then((response) => encodeDataURI(JSON.stringify(response)));\n}\n\n// _Idx\n//\n// Reject.\n//\n\n/**\n * Handles reassigning or returning a slice that was rejected by a sandbox.\n *\n * If the slice does not have a rejected property already, reassign the\n * slice to a new sandbox and add a rejected property to the slice to\n * indicate it has already rejected once.\n *\n * If the slice rejects with a reason, or has a rejected time stamp\n * (ie. has been rejected once already) then return all slices from the\n * job to the scheduler and terminate all sandboxes with that jobAddress.\n *\n * The sandbox will be terminated.\n *\n * @param {Sandbox} sandbox\n * @param {Slice} slice\n */\nSupervisor.prototype.handleWorkReject = async function Supervisor$handleWorkReject (sandbox, slice, rejectReason)\n{\n debugging() && console.debug('handleWorkReject', rejectReason, slice.rejectedTimeStamp, slice.identifier);\n\n // Do a hard flush of the microtask queue and finish the current event loop.\n await new Promise((resolve) => setImmediate(() => setTimeout(resolve, 0)));\n\n const jobManager = slice.jobManager;\n jobManager.rejectedJobReasons.push(rejectReason); // memoize reasons\n\n // First time rejecting without a reason. Try assigning slice to a new sandbox.\n if (rejectReason === 'false' && !slice.rejectedTimeStamp)\n {\n // Set rejected time stamp.\n slice.rejectedTimeStamp = Date.now();\n // Schedule the slice for execution.\n jobManager.scheduleSlice(slice, true /* placeInTheFrontOfTheQueue*/);\n // Slice has been rescheduled, but we still need to terminate the sandbox.\n jobManager.returnSandbox(sandbox);\n }\n else\n { \n // Slice has a reason OR rejected without a reason already and got stamped.\n // Add to array of rejected jobs.\n let rejectedJob = {\n address: slice.jobAddress,\n reasons: jobManager.rejectedJobReasons,\n }\n this.rejectedJobs.push(rejectedJob);\n\n // Purge the job.\n this.purgeJob(jobManager);\n\n // Tell everyone all about it, when allowed.\n if (jobManager.displayMaxDiagInfo)\n {\n const suffixMsg = '\\n\\tAll slices with the same jobAddress returned to the scheduler.\\n\\tAll sandboxes with the same jobAddress are terminated.';\n if (slice.rejectedTimeStamp)\n this.emit('warning', `work.reject: The slice ${slice.identifier} was rejected twice.${suffixMsg}`);\n else\n this.emit('warning', `work.reject: The slice ${slice.identifier} was rejected with reason ${rejectReason}.${suffixMsg}`);\n }\n }\n}\n\n// _Idx\n//\n// Unused functions that we need to review.\n// 1) destroy, shutdown, halt -- possibly need to incorporate these ideas in stopWork\n// 2) predictLoad -- XXXpfr: I really feel bad about not being able to figure out how to incorporate\n// this into the design of sup2. This was a central part of Wes' design of sup2.\n// I need to collaborate with Wes to resolve my ignorance.\n//\n\n/**\n * UNUSED\n * @deprecated -- may use later\n **/\nSupervisor.prototype.destroy = function Supervisor$destory()\n{\n selectiveDebugging && console.debug(`Supervisor.screenSaverDestroy: destroying Supervisor and everything else.`);\n this.stopWork(true /*forceTerminate*/);\n if (this.state) this.state.destroy();\n if (this.progressReportTimer) clearInterval(this.progressReportTimer);\n if (this.watchdogTimer) clearInterval(this.watchdogTimer);\n this.state = null;\n this.progressReportTimer = null;\n this.watchdogTimer = null;\n this.jobManagerInventory = null;\n this.sandboxInventory = [];\n this.readiedSandboxes = [];\n this.closeConnections();\n}\n\n/**\n * UNUSED\n * @deprecated -- may use later \n * Halt the Supervisor as quickly as possible.\n **/\nSupervisor.prototype.halt = function Supervisor$halt()\n{\n this.state.setIf('ready', 'stopping');\n if (!this.state.is('stopping'))\n throw new Error(`Supervisor has an invalid state ${this.state} for halt`);\n clearInterval(this.watchdogTimer);\n\n for (let jobMan of this.jobManagerInventory)\n {\n jobMan.state.setIf('ready', 'stop');\n for (const sandbox of jobMan.workingSandboxes)\n {\n sandbox.stop(); // NYI -- will terminate.\n }\n }\n}\n \n/**\n * UNUSED\n * @deprecated -- may use later \n * Shutdown the supervisor; attempts to return work which will not be finished before timeout expires.\n * The shutdown is complete once this supervisor emits the stopped state change.\n */\nSupervisor.prototype.shutdown = function Supervisor$shutdown(timeoutMs)\n{\n var ps = [], returnSliceInventory = [];\n var timer;\n\n this.state.setIf('ready', 'stopping');\n if (!this.state.is('stopping'))\n throw new Error(`Supervisor has an invalid state ${this.state} for shutdown`);\n clearInterval(this.watchdogTimer);\n\n for (let jobMan of this.jobManagerInventory)\n {\n jobMan.state.setIf('ready', 'stop');\n\n for (let slice of jobMan.sliceInventory)\n {\n if (slice.state.is('initial') || slice.state.is('ready'))\n {\n returnSliceInventory.push(slice);\n }\n else if (slice.state.is(sliceStatus.working))\n {\n ps.push(new Promise((resolve, reject) => {\n slice.state.on('change', (status) => {\n if (status === 'done')\n resolve();\n });\n }));\n }\n }\n }\n\n const reason = 'Supervisor.shutdown';\n this.returnSlices(returnSliceInventory, reason);\n timer = setTimeout(this.halt.bind(this), timeoutMs);\n Promise.all(ps)\n .then(() => {\n clearTimeout(timer);\n this.state.set('stopping', 'stopped');\n })\n .catch((e) => {\n if (e.code !== 'DCP_SUPERVISOR_ESYNC')\n throw e; /* becomes unhandled rejection */\n });\n}\n\n/** \n * Factory function which generates a list of origins which are safe to communicate \n * with for this purpose. Currently-valid purposes (more will be added):\n * - any\n * - fetchData\n * - fetchWorkFunctions\n * - fetchArguments\n * - sendResults\n */\nSupervisor.prototype.makeSafeOriginList = function Supervisor$$makeSafeOriginList(purpose)\n{\n var list = [];\n \n if (this[purpose])\n list = list.concat(this[purpose]);\n \n /* Add 'any' origin(s) to list iff not in localExec, or in localExec and purpose is sendResults */\n if (!this.options.localExec || (this.options.localExec && purpose === 'sendResults'))\n list = list.concat(this.allowedOrigins)\n \n return list;\n}\n \n /**\n * UNUSED -- DOES NOT WORK YET.\n * NEED TO WORK WITH WES TO FIGURE OUT BEST WAY TO GET PREDICTLOAD TO WORK.\n * Predict the load on this supervisor based on the local job measurement data.\n * Works by looking at current conditions and available slices, and tries to guess\n * in what order they will be finished, working, etc. \n *\n * The simulation is very naive, but is expected to be accurate several seconds\n * into the future, particularly as we approach the end of a task.\n *\n * @param {number} whenMs \n * \n * @returns {Object<load, jobManagerInventory>} where load is and instance of Load and the predicted \n * load at the prediction time, and jobManagerInventory \n * is a counterfeit which holds the predicted state of \n * the jobManagerInventory at that time.\n */\nSupervisor.prototype.predictLoad = function Supervisor$predictLoad (whenMs)\n{\n /** @type {JobManager[]} */\n var jmi = new Inventory(); /* Inventory of counterfeit JobManagers. */\n var load = new Load(0,0); /* This \"current\" load throughout the prediction. */\n /** @type {Slice} */\n var next; /* The next slice to \"finish\". */\n\n /* Initialize data structures for prediction from current activity. */\n for (let jobMan of this.jobManagerInventory.filter(jm => jm.state.is('ready') && jm.sliceInventory.length))\n {\n jobMan = jobMan.counterfeit();\n jmi.push(jobMan);\n jobMan.sliceInventory.forEach((s) => s.state.setIf('initial', 'ready'));\n }\n next = findNextSlice();\n \n /**\n * Routine that finds the slice that will end next (soonest.)\n * @returns {Slice}\n */\n function findNextSlice()\n {\n /** @type {Slice} */\n var _next;\n for (let jobMan of jmi)\n {\n const _workingSlices = jobMan.workingSlices;\n for (let slice of _workingSlices)\n {\n //\n // slice.etaMs is the estimated time interval until slice execution completes.\n //\n // If the slice hasn't started,\n // slice.etaMs = slice.jobManager.estimateWallMs,\n // else if the slice has completed execution:\n // slice.etaMs = 0.\n // else if the slice has started:\n // slice.jobManager.estimateWallMs - (Date.now() - slice.startTime).\n //\n if (_next && (_next.etaMs <= slice.etaMs))\n continue;\n\n _next = slice;\n }\n }\n load.add(_next.jobManager.metrics);\n \n return _next;\n }\n\n /* At this point, jmi is an Inventory of counterfeit job managers that are \"ready\" for\n * work, next.etaMs is the time interval until the next slice will finish, and we have\n * a reasonably accurate picture of our current load.\n *\n * Next, we \"end\" this slice, try to fill all cores, and push the timeline forward to\n * the next predicted end of slice.\n */\n for (next = findNextSlice();\n next && (next.etaMs < whenMs);\n next = findNextSlice())\n {\n let ended = next;\n let cursor = this.makeJobSelectionCursor(jmi);\n\n /* \"end\" this slice */\n load.subtract(ended.jobManager.metrics);\n /* Fake out collecting result to transition state to FINISHED. */\n ended.collectResult(null);\n\n /* \"start\" as many slices as we can - given our CPU/GPU constraints, slice data in memory, etc */\n while (this.targetLoad.fits(load))\n {\n let slice = cursor.next();\n if (!slice)\n break; /* Running out of work that fits. */\n\n if (!load.fits(this.targetLoad, slice.jobManager.metrics))\n continue;\n\n /* Pick a ready slice from this job and add its anticipated load to our current load if it will fit */\n slice = slice.jobManager.readySlices.shift();\n slice.markAsWorking(); // ?? Not sure this is correct.\n //slice.etaMs = ended.etaMs + slice.jobManager.estimateWallMs; wtf?!?! <--- LOOK HERE\n\n load.add(slice.jobManager.metrics);\n }\n }\n\n return { load, jobManagerInventory: jmi };\n}\n\n\n//# sourceURL=webpack://dcp/./src/dcp-client/worker/supervisor2/index.js?");
|
|
4528
|
+
eval("/* provided dependency */ var process = __webpack_require__(/*! ./node_modules/process/browser.js */ \"./node_modules/process/browser.js\");\n/**\n * @file dcp-client/worker/supervisor2/index.js\n * Code managing sandboxes, tasks, jobs, and slices within in a DCP Worker.\n * @author Wes Garland, wes@kingsds.network\n * Paul, paul@kingsds.network\n * @date Dec 2020\n * June 2022\n * @module supervisor\n */\n\n/* global dcpConfig */ // eslint-disable-line no-redeclare\n// @ts-check\n\n\nconst DCP_ENV = __webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\");\nconst { requireNative } = __webpack_require__(/*! dcp/dcp-client/webpack-native-bridge */ \"./src/dcp-client/webpack-native-bridge.js\");\nconst dcp4 = __webpack_require__(/*! dcp/protocol-v4 */ \"./src/protocol-v4/index.js\");\nconst debugging = (__webpack_require__(/*! dcp/debugging */ \"./src/debugging.js\").scope)('worker');\nconst constants = __webpack_require__(/*! dcp/common/scheduler-constants */ \"./src/common/scheduler-constants.js\");\nconst { setImmediate } = __webpack_require__(/*! dcp/common/dcp-timers */ \"./src/common/dcp-timers.js\");\nconst { EventEmitter } = __webpack_require__(/*! dcp/common/dcp-events */ \"./src/common/dcp-events/index.js\");\nconst { DCPError } = __webpack_require__(/*! dcp/common/dcp-error */ \"./src/common/dcp-error.js\");\nconst { Keystore, Address } = __webpack_require__(/*! dcp/dcp-client/wallet */ \"./src/dcp-client/wallet/index.js\");\nconst { assert } = __webpack_require__(/*! dcp/common/dcp-assert */ \"./src/common/dcp-assert.js\");\nconst { localStorage } = __webpack_require__(/*! dcp/common/dcp-localstorage */ \"./src/common/dcp-localstorage.js\");\nconst { DcpURL } = __webpack_require__(/*! dcp/common/dcp-url */ \"./src/common/dcp-url.js\");\nconst RingBuffer = __webpack_require__(/*! dcp/utils/ringBuffer */ \"./src/utils/ringBuffer.js\");\nconst { Synchronizer } = __webpack_require__(/*! dcp/common/concurrency */ \"./src/common/concurrency.js\");\nconst { JobManager } = __webpack_require__(/*! ./job-manager */ \"./src/dcp-client/worker/supervisor2/job-manager.js\");\nconst { Load } = __webpack_require__(/*! ./load */ \"./src/dcp-client/worker/supervisor2/load.js\");\nconst { Sandbox, SandboxError } = __webpack_require__(/*! ./sandbox2 */ \"./src/dcp-client/worker/supervisor2/sandbox2.js\");\nconst { sliceStatus } = __webpack_require__(/*! dcp/common/scheduler-constants */ \"./src/common/scheduler-constants.js\");\nconst hash = __webpack_require__(/*! dcp/common/hash */ \"./src/common/hash.js\");\nconst { calculateJoinHash } = __webpack_require__(/*! dcp/dcp-client/compute-groups */ \"./src/dcp-client/compute-groups/index.js\");\nconst { ModuleCache } = __webpack_require__(/*! ./module-cache */ \"./src/dcp-client/worker/supervisor2/module-cache.js\");\nconst { Inventory, leafMerge, a$sleepMs, ms, pct, generateOpaqueId, booley, compressJobMap, \n toJobMap, truncateAddress, encodeDataURI, makeValueURI, justFetch, stringify } = __webpack_require__(/*! dcp/utils */ \"./src/utils/index.js\");\n//const { CodeFlow } = require('client-oauth2');\n//const { text } = require('./lang').getLocaleStrings('en_CA'); /** XXXpfr @todo Don't know what to do with localization? */\n\n/** @typedef {import('dcp/dcp-client/wallet/eth').Address} Address */\n/** @typedef {import('dcp/protocol-v4/connection/connection').Connection} Connection */\n/** @typedef {string} opaqueId */ // 22 character base64 string\n/** @typedef {import('..').Worker} Worker */\n/** @typedef {import('..').SupervisorOptions} SupervisorOptions */\n/** @typedef {import('./slice2').Slice} Slice */\n/** @typedef {import('dcp/utils').SliceMessage} SliceMessage */\n\nconst addressTruncationLength = 20;\n\n//\n// Configs are moving around in dcpConfig and local worker configs, so set up some defaults.\nlet workerTuning = dcpConfig.worker;\nif (!workerTuning) workerTuning = dcpConfig.Supervisor;\nif (!workerTuning || !workerTuning.dcp || !workerTuning.dcp.tuning || !workerTuning.dcp.tuning.watchdogInterval\n || !workerTuning.sandbox || !workerTuning.allowOrigins || !workerTuning.minimumWage || !workerTuning.computeGroups)\n workerTuning = {\n dcp: {\n tuning: { watchdogInterval: 7, minSandboxStartDelay: 0.1, maxSandboxStartDelay: 0.7 },\n connectionOptions: { default: { identityUnlockTimeout: 15 * 60 /* seconds */ } },\n },\n sandbox: { progressReportInterval: 2 * 60 * 100 },\n allowOrigins: { fetchWorkFunctions: [], fetchArguments: [], fetchData: [], sendResults: [], any: [] },\n minimumWage: { CPU: 0, GPU: 0, 'in': 0, out: 0 },\n leavePublicGroup: false,\n pCores: 0,\n computeGroups: {},\n // The following configs are not in dcpConfig or worker configs (yet), but may be specified in local worker configs to override the defaults.\n pruneFrequency: 15 * 1000, // Maxiumum time interval where we check to prune used sandboxes.\n workerSandboxThreshold: 7, // When maxWorkingSandboxes >= workerSandboxThreshold, we allow an extra 25% of assigned sandboxes that won't be pruned.\n cachedJobsThreshold: 12, // Prune the unused job managers >= cachedJobsThreshold.\n};\n\n//\n// Flags for tracing.\n//\nconst selectiveEnable = false;\nconst displayWarnError = false || selectiveEnable;\nconst selectiveDebugging = selectiveEnable || debugging();\nconst debuggingError = false || selectiveDebugging || displayWarnError;\nconst debuggingWarn = false || selectiveDebugging || displayWarnError;\nconst selectiveDebugging2 = selectiveEnable && false || debugging('supervisor');\nconst displaySliceState = true;\nconst displayCompletedResults = false;\n\n/** \n * Adjust delay times when debugging.\n * The adjustment for niim is automatic, other debuggers must manually change this value.\n */\nlet timeDilation = 1;\nif (DCP_ENV.platform === 'nodejs')\n{\n /** Make timers 10x slower when running in niim */\n timeDilation = (requireNative('module')._cache.niim instanceof requireNative('module').Module) ? 10 : 1;\n}\n\n//\n// Index to functionality -- search for '_Idx' to toggle through the index.\n//\n// 1) Ctor: Supervisor constructor.\n// 2) Important property-like functions.\n// 3) Dtors: screenSaverDestroy, stopWork, purgeJob.\n// 4) Connection code.\n// 5) Work: Distribute slice to sandboxes.\n// 6) Return slices and sent progress reports to result-submitter-results.\n// 7) Task Distributor (TD): requestTask (Rq) support -- communication with TD.\n// 8) Aggregators from the job managers.\n// 9) Sandbox creation and management.\n// 10) Result-submitter-result support functions.\n// 11) Work reject.\n// 12) Unused functions that we need to review.\n//\n\n// _Idx\n//\n// Ctor: Supervisor constructor.\n//\n\n/** \n * Supervisor constructor\n * \n * A supervisor manages the communication with the scheduler, manages sandboxes, and\n * decides which workload should be sent to which sandboxes when.\n *\n * Start state:\n * - initial\n *\n * Intermediate states:\n * - ready\n * - stopping\n *\n * Terminal states:\n * - stopped\n *\n * Valid transitions:\n * - initial -> ready where that happens \n * - ready -> stopping\n * - stopping -> stopped\n *\n * @param {Worker} worker - The worker that created this instance.\n * @param {SupervisorOptions} options - Options for specifying custom behaviour and tuning,\n */\nfunction Supervisor(worker, options)\n{\n assert(options.identity instanceof Keystore);\n assert(options.paymentAddress instanceof Address);\n\n /**\n * Flag to indicate a debug build.\n * Used when we want to display extra information and do extra checks for developers only.\n * @type {boolean}\n */\n this.debugBuild = ((__webpack_require__(/*! dcp/common/dcp-build */ \"./src/common/dcp-build.js\").build) === 'debug');\n /**\n * When Supervisor.sliceTiming is set to be true, it displays the timings of a every slice\n * slice['queueingDelta'] = timespan of when slice is passed to jobManager.runQueuedSlice until sandbox.work\n * slice['executionDelta'] = timespan of execution in sandbox\n * slice['resultDelta'] = timespan of when sandbox finishes executing until recordResult completes.\n * @type {boolean}\n */\n this.sliceTiming = false;\n /** Used for analyzing the completed results in Supervisor.recordResult. */\n this.resultMap = {};\n\n /** @type {ModuleCache} */\n this.moduleCache = new ModuleCache(this);\n\n this.worker = worker;\n this.identity = options.identity;\n this.paymentAddress = options.paymentAddress;\n this.options = options;\n this.maxWorkingSandboxes = options.maxWorkingSandboxes || 1;\n this.maxTotalSandboxes = this.maxWorkingSandboxes;\n\n // We're making the assumption that if a worker has at least 7 sandboxes, \n // then the worker has sufficient resources to handle 25% more sandboxes in memory.\n // This assumption may be overridden by changing workerSandboxThreshold.\n if (this.maxWorkingSandboxes >= this.workerSandboxThreshold)\n this.maxTotalSandboxes = Math.ceil(1.25 * this.maxWorkingSandboxes);\n // When # of sandboxes reaches this level, we more aggressively prune.\n this.mustPruneSandboxLevel = Math.ceil(1.5 * this.maxTotalSandboxes);\n // Last prune time stamp.\n this.lastPrune = 0;\n // General time stamp.\n this.lastTime = 0;\n\n // Supervisor may get created by Worker where options.cores or options.targetLoad is not defined.\n this.numCPU = this.maxWorkingSandboxes;\n this.numGPU = 1;\n this.portionToUseCPU = pct(100);\n this.portionToUseGPU = pct(100);\n\n if (options.cores)\n {\n this.numCPU = options.cores.cpu || this.numCPU;\n this.numGPU = options.cores.gpu || this.numGPU;\n }\n if (options.targetLoad)\n {\n this.portionToUseCPU = options.targetLoad.cpu || pct(100);\n this.portionToUseGPU = options.targetLoad.gpu || pct(100);\n }\n\n this.tuning = {\n maxCPUAlloc: this.portionToUseCPU, /**< Maximum proportion of CPU time to attempt to use. */\n maxGPUAlloc: this.portionToUseGPU, /**< Maximum proportion of GPU time to attempt to use. */\n watchdogInterval: 7, /**< (seconds) How frequently to kick off an unsolicited requestTask. */\n prefetchInterval: 20, /**< (seconds) How many seconds into the future are looking to project capacity during work fetch. */\n minSandboxStartDelay: 0.1, /**< (seconds) Base minimum of this.delayMs, scaled by this.delayScaler. */\n maxSandboxStartDelay: 0.7, /**< (seconds) Base maximum random component of this.delayMs, scaled by this.delayScaler. */\n };\n this.tuning = leafMerge(this.tuning, workerTuning.dcp.tuning);\n if (options.watchdogInterval > 0)\n this.tuning.watchdogInterval = options.watchdogInterval; // Override.\n //this.tuning.watchdogInterval = 0.25;\n\n /**\n * Fine tune this.delayMs.\n * Note: Please discuss any change with Paul, paul@kingsds.network.\n * XXXpfr @todo Finalize the delay tuning.\n */\n this.delayScaler = 0.5;\n\n debugging('supervisor') && console.debug('Supervisor.tuning', this.tuning);\n\n /**\n * Note: targetLoad is not properly implemented yet.\n * XXXpfr @todo Collaborate with Wes to get it right.\n * @type {Load}\n */\n this.targetLoad = new Load({\n cpu: Math.min(this.maxWorkingSandboxes, this.numCPU),\n gpu: Math.min(this.maxWorkingSandboxes, this.numGPU)\n }).scale(this.tuning.maxCPUAlloc, this.tuning.maxGPUAlloc);\n\n /** @type {string[]} */\n this.allowedOrigins = workerTuning.allowOrigins.any;\n /** @type {string[]} */\n this.fetchWorkFunctions = workerTuning.allowOrigins.fetchWorkFunctions;\n /** @type {string[]} */\n this.fetchArguments = workerTuning.allowOrigins.fetchArguments;\n /** @type {string[]} */\n this.fetchData = workerTuning.allowOrigins.fetchData;\n /** @type {string[]} */\n this.sendResults = workerTuning.allowOrigins.sendResults;\n\n // In localExec, do not allow work function or arguments to come from the 'any' origins\n if (this.options.localExec)\n {\n this.allowedOrigins = this.allowedOrigins.concat(options.allowedOrigins.any);\n this.fetchWorkFunctions = this.fetchWorkFunctions.concat(options.allowedOrigins.fetchWorkFunctions);\n this.fetchArguments = this.fetchArguments.concat(options.allowedOrigins.fetchArguments);\n this.fetchData = this.fetchData.concat(options.allowedOrigins.fetchData);\n this.sendResults = this.sendResults.concat(options.allowedOrigins.sendResults)\n }\n\n if (options.allowedOrigins && options.allowedOrigins.length > 0)\n this.allowedOrigins = options.allowedOrigins.concat(this.allowedOrigins);\n \n //\n // The following 3 configs are not in dcpConfig or worker configs (yet), but may be specified in local worker configs to override the defaults.\n //\n /** @type {number} - Maxiumum time interval where we check to prune used sandboxes. */\n this.pruneFrequency = workerTuning.pruneFrequency || 15 * 1000;\n /** @type {number} - When maxWorkingSandboxes >= workerSandboxThreshold, we allow an extra 25% of assigned sandboxes that won't be pruned. */\n this.workerSandboxThreshold = workerTuning.workerSandboxThreshold || 7;\n /** @type {number} - Prune the unused job managers >= cachedJobsThreshold. */\n this.cachedJobsThreshold = workerTuning.cachedJobsThreshold || 12;\n\n /** @type {Object.<Address, JobManager>} */\n this.jobMap = {}; \n /** @type {Sandbox[]} - All sandboxes that are being used by the job managers. Makes sure we don't lose sandboxes. */\n this.sandboxInventory = [];\n /** @type {Sandbox[]} - Started sandboxes that are not in sandboxInventory yet. */\n this.readiedSandboxes = [];\n /** @type {JobManager[]} */\n this.jobManagerInventory = new Inventory('jobManagers');\n /** @type {Synchronizer} */\n this.state = new Synchronizer('initial', [ 'initial', 'ready', 'reconnecting', 'stopping', 'stopped', 'broken']);\n\n /** @type {string} */\n this.lastDcpsid = undefined;\n /** @type {Connection} */\n this.taskDistributor = null;\n /** @type {Connection} */\n this.resultSubmitter = null;\n /** @type {Connection} */\n this.eventRouter = null;\n /** @type {Connection} */\n this.packageManager = null;\n /** @type {Array<object>} */\n this.resultSubmitterMessageQueue = [];\n /** @type {Array<object>} */\n this.eventRouterMessageQueue = [];\n\n /** @type {object} */\n this.schedulerConfig = leafMerge(dcpConfig.scheduler, options.schedulerConfig);\n\n /** @type {opaqueId} */\n this.workerId = localStorage.getItem('workerId');\n if (!this.workerId || this.workerId.length !== constants.workerIdLength)\n {\n this.workerId = generateOpaqueId();\n localStorage.setItem('workerId', this.workerId);\n }\n /** @type {object[]} */\n this.rejectedJobs = [];\n /** \n * An N-slot ring buffer of job addresses. Stores all jobs that have had no more than 1 slice run in the ring buffer.\n * Required for the implementation of discrete jobs \n * @type {RingBuffer} \n */\n this.ringBufferofJobs = new RingBuffer(100); // N = 100 should be more than enough. \n /** @type {boolean} - pseudo-mutex guarding requestTask. */\n this.isFetchingNewWork = false;\n\n // Start up the connections.\n this.instantiateAllConnections();\n\n /**\n * Note: DCP-3241 asks to test Android to see if we need this restriction any longer.\n * XXXpfr @todo Hopefully we can delete this @hack.\n */\n // @hack - dcp-env.isBrowserPlatform is not set unless the platform is _explicitly_ set,\n // using the default detected platform doesn't set it.\n // Fixing that causes an error in the wallet module's startup on web platform, which I\n // probably can't fix in a reasonable time this morning.\n // ~ER2020-02-20\n if (!options.maxWorkingSandboxes\n && DCP_ENV.browserPlatformList.includes(DCP_ENV.platform)\n && navigator.hardwareConcurrency > 1) {\n this.maxWorkingSandboxes = navigator.hardwareConcurrency - 1;\n if (typeof navigator.userAgent === 'string') {\n if (/(Android).*(Chrome|Chromium)/.exec(navigator.userAgent)) {\n this.maxWorkingSandboxes = 1;\n this.emit('warning', 'Doing work with Chromimum browsers on Android is currently limited to one sandbox');\n }\n }\n }\n}\nexports.Supervisor = Supervisor;\nSupervisor.prototype = Object.getPrototypeOf(new EventEmitter('Supervisor')); // Fake out VSCode -- get's rid of a billion red-squigglies.\nSupervisor.prototype = new EventEmitter('Supervisor');\n/**\n * Preserve the constructor property.\n * @constructor\n */\nSupervisor.prototype.constructor = Supervisor;\n\n/**\n * Set up sandboxes and interval timers, then start to search for work.\n **/\nSupervisor.prototype.startWork = function Supervisor$startWork ()\n{\n /* Provide opportunity for calling code to hook ready/error events. */\n setImmediate(async () => {\n try\n {\n if (this.state.isNot('initial'))\n {\n if (this.state.setIf('stopped', 'initial')) {}\n else if (this.state.setIf('reconnecting', 'initial')) {}\n else if (this.state.setIf('broken', 'initial')) {}\n else if (this.state.is('ready')) return\n else throw new Error(`Supervisor startWork is in unexpected state ${this.state}, aborting...`);\n }\n this.instantiateAllConnections();\n\n await this.createSandboxes(this.maxWorkingSandboxes)\n .then(() => this.checkCapabilities());\n\n // Beacon interval timer.\n this.progressReportTimer = setInterval(() => this.emitProgressReport(), (workerTuning.sandbox.progressReportInterval || 2 * 60 * 100));\n // Watchdog: requestTask-driven interval timer.\n this.watchdogTimer = setInterval(() => this.requestTask() , ms(this.tuning.watchdogInterval));\n if (DCP_ENV.platform === 'nodejs' && this.options.localExec)\n {\n /* Interval timer helps keep worker alive forever, which we don't want in localExec. */\n this.progressReportTimer.unref();\n this.watchdogTimer.unref();\n }\n\n this.state.set('initial', 'ready');\n\n setImmediate(() => this.requestTask()); // Don't wait for watchdog.\n }\n catch(error)\n {\n this.state.set('initial', 'broken');\n this.emit('error', error);\n }\n });\n}\n\n/** Construct capabilities when necessary. */\nSupervisor.prototype.checkCapabilities = function Supervisor$checkCapabilities ()\n{\n if (!this.capabilities)\n {\n /**\n * Assign the capabilities of one the sandboxes before fetching slices from the scheduler.\n * @todo Remove this once fetchTask uses the capabilities of every sandbox to fetch slices.\n */\n const sandbox = this.readiedSandboxes.length > 0 ? this.readiedSandboxes[0] : this.sandboxInventory[0];\n if (sandbox)\n {\n this.capabilities = sandbox.capabilities;\n this.emit('capabilitiesCalculated', this.capabilities);\n }\n }\n\n if (DCP_ENV.isBrowserPlatform && this.capabilities.browser)\n this.capabilities.browser.chrome = DCP_ENV.isBrowserChrome;\n}\n\n// _Idx\n//\n// Important property-like functions.\n//\n\n/**\n * Universal delay milliseconds..\n * @returns {number}\n */\nSupervisor.prototype.delayMs = function Supervisor$delayMs (max = this.tuning.maxSandboxStartDelay, min = this.tuning.minSandboxStartDelay)\n{\n // Note: Please discuss any change with Paul, paul@kingsds.network.\n return 1000 * timeDilation * this.delayScaler * (min + Math.random() * (max - min));\n}\n\n/**\n * Indicates whether supervisor is ready for business.\n * @returns {boolean} - When true, the party is on...\n */\nSupervisor.prototype.isReady = function Supervisor$isReady()\n{\n return this.worker.working && this.state.is('ready');\n}\n\n/**\n * Safe access to Connection.close(...).\n * @param {Connection} connection\n * @param {string} [reason='requested']\n * @param {boolean} [immediate=false]\n * @returns {Promise<string>}\n */\nfunction safeClose(connection, reason = 'requested', immediate = false)\n{\n if (connection)\n {\n let msg;\n if (connection.state.is('closed')) msg = 'closed';\n else if (connection.state.is('closing')) msg = 'closing';\n else if (connection.state.is('close-wait')) msg = 'close-wait';\n if (msg)\n {\n debuggingWarn && console.warn(`${msg}, do not try to close again.`);\n return Promise.resolve(msg);\n }\n return connection.close(reason, immediate)\n .then(() => {\n return Promise.resolve(null);\n });\n }\n return Promise.resolve('already closed');\n}\n\n/**\n *\n * @param {string} operation\n * @param {*} data\n * @returns {Promise<string>}\n */\nSupervisor.prototype.saveForResubmitToRS = function Supervisor$saveForResubmitToRS(operation, data)\n{\n this.resultSubmitterMessageQueue.push({ operation, data });\n return safeClose(this.resultSubmitter);\n}\n\n/**\n * Error feedback to user.\n * @param {string} message\n * @param {*} extra\n */\nSupervisor.prototype.error = function Supervisor$error(message, extra)\n{\n const dcpError = new DCPError(message, extra);\n this.emit('error', dcpError);\n}\n\n/**\n * When true, the sandbox complete handler will look for another slice in the same job,\n * and if not found, then proceed to Supervisor.requestTask.\n * @returns {boolean}\n */\nSupervisor.prototype.runSliceFromSameJob = function Supervisor$runSliceFromSameJob()\n{\n //\n // Experimental, should be off by default.\n // Cf. sandbox complete handler in JobManager.hookUpSandboxListeners.\n //\n const disable = true;\n const tooManyJobs = this.activeJobCount() > this.maxWorkingSandboxes;\n return !disable && !tooManyJobs && this.unusedSandboxSlots() < 2;\n}\n\n/**\n * This function is used as the target number of sandboxes to be associated with slices and start working.\n * Warning: Do not rely on this information being 100% accurate -- it may change in the next instant.\n * @returns {number}\n */\nSupervisor.prototype.unusedSandboxSlots = function Supervisor$unusedSandboxSlots ()\n{\n return this.maxWorkingSandboxes - this.workingSliceCount();\n}\n\n/**\n * Create errorObj with error.code if it exists.\n * @param {Error} error\n * @returns {object}\n */\nSupervisor.prototype.checkCode = function Supervisor$checkCode (error)\n{\n if (!error) return '';\n const errorObj = { message: error.message };\n if (error['errorCode']) errorObj['errorCode'] = error['errorCode'];\n if (error['code']) errorObj['code'] = error['code'];\n return errorObj;\n}\n\n/**\n * Remove stack trace from error.\n * May not work when error is a string with no new-lines.\n * @param {string|Error} error\n * @returns {string|Error}\n */\nSupervisor.prototype.removeStackTrace = function Supervisor$removeStackTrace (error)\n{\n if (typeof error === 'string')\n {\n const errorLines = error.split('\\n');\n return (errorLines && errorLines.length > 0) ? errorLines[0] : error;\n }\n if (error instanceof Error)\n return error.message;\n return error;\n}\n\n// _Idx\n//\n// Dtors: screenSaverDestroy, stopWork, purgeJob.\n//\n\n/**\n * If we cannot create a new sandbox, that probably means we're on a screensaver worker\n * and the screensaver is down. So return the slices to the scheduler.\n */\nSupervisor.prototype.screenSaverDestroy = function Supervisor$screenSaverDestroy()\n{\n debugging('supervisor') && console.debug(`Supervisor.screenSaverDestroy: destroying all job managers and terminating all sandboxes.`);\n this.jobManagerInventory.forEach(jm => jm.destroy());\n this.jobManagerInventory = new Inventory('jobManagers');\n\n this.readiedSandboxes.forEach(sandbox => {\n if (!sandbox.isTerminated) sandbox.terminate(false);\n });\n this.readiedSandboxes = [];\n\n this.sandboxInventory.forEach(sandbox => {\n if (!sandbox.isTerminated) sandbox.terminate(false);\n });\n this.sandboxInventory = [];\n}\n\n/**\n * Terminates sandboxes and returns slices.\n * Sets the working flag to false, call @this.work to start working again.\n * \n * If forceTerminate is true: Terminates all sandboxes and returns all slices.\n * If forceTerminate is false: Terminates non-working sandboxes and returns initial and ready slices.\n *\n * @param {boolean} [forceTerminate = true] - true if you want to stop the sandboxes from completing their current slice.\n * @returns {Promise<void>}\n */\nSupervisor.prototype.stopWork = async function Supervisor$stopWork (forceTerminate = true)\n{\n selectiveDebugging && console.debug(`Supervisor.stopWork(${forceTerminate},${this.state}): terminating sandboxes and returning slices to scheduler.`);\n\n // Do a hard flush of the microtask queue and finish the current event loop.\n await new Promise((resolve) => setImmediate(() => setTimeout(resolve, 0)));\n\n if (this.state.setIf('ready', 'stopping')) {}\n else if (this.state.setIf('reconnecting', 'stopping')) {}\n else if (this.state.setIf('broken', 'stopping')) {}\n else if (this.state.is('stopped')) return\n else throw new Error(`Supervisor stopWork is in unexpected state ${this.state}, aborting...`);\n\n this.instantiateAllConnections();\n\n this.readiedSandboxes.forEach((sandbox) => {\n if (!sandbox.isTerminated) sandbox.terminate(false);\n });\n this.readiedSandboxes = [];\n\n if (forceTerminate)\n {\n for (const jm of this.jobManagerInventory)\n jm.destroy();\n\n this.sandboxInventory.forEach((sandbox) => {\n if (!sandbox.isTerminated) sandbox.terminate(false);\n });\n }\n else\n {\n let activeSliceCount = 0;\n const slicesToReturn = [];\n for (const jm of this.jobManagerInventory)\n {\n //jm.dumpSlices(`stopWork1:${jm.address}`);\n const queuedSlices = jm.queuedSlices;\n slicesToReturn.push(...queuedSlices);\n jm.removeSlices(queuedSlices);\n activeSliceCount += jm.activeSlices.length;\n jm.nonWorkingSandboxes.forEach((sandbox) => jm.returnSandbox(sandbox));\n }\n\n const reason = `Supervisor stopWork(${forceTerminate}): returning all non-finished slices that are not working.`;\n this.returnSlices(slicesToReturn, reason, false /*removeSlices*/);\n\n await new Promise((resolve) => {\n if (activeSliceCount === 0)\n resolve();\n // Resolve and finish work once all sandboxes have finished submitting their results.\n this.on('submitFinished', () => {\n if (--activeSliceCount === 0)\n {\n debuggingWarn && console.warn('All sandboxes empty, stopping worker and closing all connections');\n resolve();\n }\n });\n });\n\n for (const jm of this.jobManagerInventory)\n {\n //jm.dumpSlices(`stopWork2:${jm.address}`);\n jm.liveSandboxes.forEach((sandbox) => sandbox.terminate(false));\n jm._sandboxInventory = [];\n }\n }\n this.sandboxInventory = [];\n this.jobManagerInventory = new Inventory('jobManagers');\n\n this.closeConnections();\n\n this.emit('stop');\n this.state.set('stopping', 'stopped');\n}\n\n/**\n* Purge all traces of the job.\n* @param {JobManager} jobManager \n*/\nSupervisor.prototype.purgeJob = function Supervisor$purgeJob (jobManager)\n{\n selectiveDebugging && console.debug(`Supervisor.purgeJob ${jobManager.identifier}.`);\n this.jobManagerInventory.delete(jobManager);\n this.moduleCache.removeJob(jobManager.address);\n jobManager.destroy();\n}\n\n// _Idx\n//\n// Connection code.\n//\n\n/** \n * Connect the supervisor to a given scheduler sub-service. Reconnection \"DDoS\" from workers\n * mitigated via an exponential backoff algorithm.\n *\n * DCPv4 connections are lazily-initiated. Successful connection establishment detected by\n * observing the payload event, which normally triggers during versioning.\n */\nSupervisor.prototype.connectTo = function Supervisor$connectTo(which)\n{\n const config = (which === 'packageManager') ? dcpConfig.packageManager : this.schedulerConfig.services[which];\n const retryMinSleepMs = 100;\n const payloadResetCount = 3; /* How many payloadCount before we reset retryNextSleepMs. */\n \n var retryNextSleepMs = retryMinSleepMs;\n var payloadCount = 0;\n var options = leafMerge(/* ordered from most to least specific */\n workerTuning.dcp.connectionOptions.default,\n workerTuning.dcp.connectionOptions[which],\n workerTuning.dcp.connectionOptions[config.location.href],\n );\n\n /**\n * The payload event handler is an interesting way to handle exponential backoff\n * for the delay in this.reconnect.\n * XXXpfr @todo Revisit exponential backoff for this.reconnect in Sup2 Part II.\n */\n\n const handlePayloadEventFn = (ev) =>\n {\n if (++payloadCount === payloadResetCount)\n {\n this[which].removeEventListener('payload', handlePayloadEventFn);\n retryNextSleepMs = retryMinSleepMs; \n }\n }\n\n this[which] = new dcp4.Connection(config, this.identity, options);\n\n debugging() && console.debug(`Opening new connection ${this[which].id} to ${which}.`)\n\n const messageQueue = this[which + 'MessageQueue'];\n if (messageQueue && messageQueue.length > 0)\n resendRejectedMessages(this[which], messageQueue);\n\n this[which].on('payload', handlePayloadEventFn);\n\n this['reconnect' + which] = this.reconnect.bind(this, which);\n this[which].on('close', () => { this['reconnect' + which](); });\n}\n\n/**\n * Reconnect logic.\n * @param {string} which -- Name of the connection \n */\nSupervisor.prototype.reconnect = async function Supervisor$reconnect(which)\n{\n debugging('supervisor') && console.debug(`Supervisor.reconnect: ${which}:`, this.state.valueOf(), this[which].state.valueOf());\n const stateChange = this.state.testAndSet('ready', 'reconnecting');\n if (stateChange || this.state.is('reconnecting'))\n {\n await a$sleepMs(100); // Sleep for 100ms, maybe exp-backoff later if we need it.\n this.connectTo(which);\n if (stateChange) this.state.set('reconnecting', 'ready');\n debugging() && console.debug(`Supervisor.reconnect: Trying to reconnect ${which}`, this.state.valueOf(), this[which].state.valueOf());\n }\n}\n\n/**\n * Close a connection properly.\n * @param {string} which -- Name of the connection to close.\n */\nSupervisor.prototype.closeConnection = function Supervisor$closeConnection(which)\n{\n if (this[which])\n {\n this[which].off('close', this['reconnect' + which]);\n this[which].close();\n this[which] = null;\n }\n}\n\n/**\n * Close all connections.\n */\nSupervisor.prototype.closeConnections = function Supervisor$closeConnections()\n{\n /** XXXpfr @todo Remove when bug DCP-3094 is fixed. */\n a$sleepMs(500);\n\n this.closeConnection('taskDistributor');\n this.closeConnection('resultSubmitter');\n this.closeConnection('eventRouter');\n this.closeConnection('packageManager');\n}\n\n/**\n * Broadcast keepalive to all connections and optionally start up sandboxes.\n * @param {boolean} [createSandbox=false] - When true, creates sandboxes for unused sandbox slots.\n */\nSupervisor.prototype.keepalive = function Supervisor$keepalive(createSandbox = false)\n{\n return Promise.all([\n this.taskDistributor.keepalive(),\n this.resultSubmitter.keepalive(),\n this.eventRouter .keepalive(),\n this.packageManager .keepalive(),\n (createSandbox ? this.createSandboxes(this.maxWorkingSandboxes) : Promise.resolve()),\n ]);\n}\n\n/**\n * Open all connections. Used when supervisor is instantiated or stopped/started to initially open connections.\n */\nSupervisor.prototype.instantiateAllConnections = function Supervisor$instantiateAllConnections ()\n{\n if (!this.taskDistributor)\n this.connectTo('taskDistributor');\n if (!this.eventRouter)\n this.connectTo('eventRouter');\n if (!this.resultSubmitter)\n this.connectTo('resultSubmitter');\n if (!this.packageManager)\n this.connectTo('packageManager');\n}\n\n/**\n * Try sending messages that were rejected on an old instance of the given connection.\n * @param {Connection} connection\n * @param {Array<object>} messageQueue\n */\nasync function resendRejectedMessages (connection, messageQueue)\n{\n var message = messageQueue.shift();\n do \n {\n try\n {\n await connection.send(message.operation, message.data);\n }\n catch (error)\n {\n debuggingError && console.error(`Failed to resend message ${message.operation} to scheduler: ${error}. Will try again on a new connection.`);\n messageQueue.unshift(message);\n safeClose(connection);\n break;\n }\n message = messageQueue.shift();\n } while (message);\n}\n \n// _Idx\n//\n// Work: Distribute slice to sandboxes.\n//\n\n/** \n * UNUSED\n * @deprecated\n * Round-robin through the job managers, picking 1 slice to run each time.\n * Try to have the same number of working sandboxes for each job.\n * Try to run a slice on every available sandbox.\n */\nSupervisor.prototype.workOnCurrentTask = function Supervisor$workOnCurrentTask ()\n{\n return this.roundRobinSlices();\n}\n\n/**\n * This is for compatibility with Supervisor1 in sa worker.\n * When we get rid of Supervisor1 we can delete the ref in sa worker and then get rid of this function.\n */\nSupervisor.prototype.setDefaultIdentityKeystore = function Supervisor$setDefaultIdentityKeystore ()\n{\n}\n\n/**\n * Called in Worker.start().\n * Initial entry point after Worker constructor.\n * We need to start searching for work here to allow starting and stopping a worker.\n */\nSupervisor.prototype.work = function Supervisor$work ()\n{\n // Set up sandboxes and interval timers, then start to search for work.\n this.startWork();\n}\n\n/** \n * Round-robin through the job managers, picking 1 slice to run each time.\n * Try to have the same number of working sandboxes for each job.\n * Try to run a slice on every available sandbox.\n */\nSupervisor.prototype.roundRobinSlices2 = function Supervisor$roundRobinSlices2 ()\n{\n try\n {\n let first = true;\n const cursor = this.makeJobSelectionCursor(this.jobManagerInventory);\n while (true)\n {\n const slice = cursor.next();\n if (!slice) break; /* No more work can fit. */\n debugging('supervisor') && console.debug('roundRobinSlices: Executing slice', slice.identifier);\n slice.markAsReserved();\n slice.jobManager.runSlice(slice, first ? 0 : this.delayMs());\n first = false;\n }\n }\n finally\n {\n this.isFetchingNewWork = false;\n }\n}\n\n/**\n * We try to balance so that each job has the same number of working slices.\n *\n * NOTES:\n * 1) If count is such that it cannot be distributed evenly, we do not yet randomly assign the extras.\n * E.g. 3 jobs, j1, j2, j3: count = 5 -- so 2 jobs get extras -- the extras need to be assigned randomly.\n * @todo Assign the extras randomly.\n * 2) this.roundRobinSlices is not going to be what we use when sup2 is in final form.\n * We want to this.makeJobSelectionCursor and something like this.roundRobinSlices2\n *\n * In the outer loop,\n * when numworkingSandboxes=1, try to get a slice running for each job with 0 working sandboxes.\n * when numworkingSandboxes=2, try to get a slice running for each job with 1 working sandboxes.\n * when numworkingSandboxes=3, try to get a slice running for each job with 2 working sandboxes. Etc.\n * The idea is to balance the number of slices working on each job.\n * @param {number} [count=0] - The number of new slices to try to get running in sandboxes.\n */\nSupervisor.prototype.roundRobinSlices = function Supervisor$roundRobinSlices (count = 0)\n{\n try\n {\n if (!count) count = this.unusedSandboxSlots();\n if (!count || this.readySliceCount() < 1)\n return Promise.resolve();\n\n const slices = [];\n let numScheduled = 0\n let readyJobManagers = this.jobManagerInventory.filter(jm => jm.ready);\n let first = true;\n selectiveDebugging && console.debug('roundRobinSlices: START count', count, 'numJobMgrs', readyJobManagers.length, 'totalWorking(w/r/wo/wsbx/sbx)', this.workingSliceCount(), this.reservedSliceCount(), this.workingSliceOnlyCount(), this.workingSandboxCount(), this.sandboxCount());\n\n for (let numWorkingSandboxes = 1; numWorkingSandboxes <= this.maxWorkingSandboxes; numWorkingSandboxes++)\n {\n let sliceCount = 0;\n const beginNumScheduled = numScheduled;\n for (const jobMan of readyJobManagers)\n {\n const _readySlices = jobMan.readySlices;\n sliceCount += _readySlices.length\n const skip = numWorkingSandboxes <= jobMan.workingSlices.length;\n\n if (skip || _readySlices.length < 1)\n {\n // Noisy log message turned off by default.\n //debugging('supervisor') && console.debug('RRS0(numS, beginNumS, count, sliceCount, skip, _ready, numWorkingS(loop), workingSlices):', numScheduled, beginNumScheduled, count, sliceCount, skip, _readySlices.length, numWorkingSandboxes, jobMan.workingSlices.length);\n continue;\n }\n\n const slice = _readySlices[0];\n slices.push(slice);\n\n slice.markAsReserved();\n jobMan.runSlice(slice, first ? 0 : this.delayMs());\n\n first = false;\n if (++numScheduled >= count)\n break;\n }\n if (numScheduled >= count)\n {\n debugging('supervisor') && console.debug('RRS1(numS, beginNumS, count, sliceCount):', numScheduled, beginNumScheduled, count, sliceCount);\n break;\n }\n if (beginNumScheduled === numScheduled && sliceCount < 1)\n {\n debugging('supervisor') && console.debug('RRS2(numS, beginNumS, count, sliceCount):', numScheduled, beginNumScheduled, count, sliceCount);\n break;\n }\n }\n if (selectiveDebugging)\n {\n console.debug(`roundRobinSlices(working:(w/r/wo/wsbx/sbx)${this.workingSliceCount()},${this.reservedSliceCount()},${this.workingSliceOnlyCount()},${this.workingSandboxCount()},${this.sandboxCount()}): Started ${slices.length}/${numScheduled}/${count} scheduled slices`, compressSlices(slices));\n const sliceGrouper = {};\n slices.forEach((slice) => {\n const jm = slice.jobManager;\n if (!sliceGrouper[slice.jobAddress]) sliceGrouper[slice.jobAddress] = { cnt: 1, working: jm.workingSlices.length, queued: jm.queuedSlices.length, assigned: jm.assignedSandboxes.length, estimation: jm.isEstimation };\n else sliceGrouper[slice.jobAddress].cnt++;\n });\n console.debug(sliceGrouper);\n }\n if (selectiveDebugging2)\n {\n const jobGrouper = {};\n this.jobManagerInventory.forEach((jm) => {\n jobGrouper[jm.address] = { working: jm.workingSlices.length, queued: jm.queuedSlices.length, assigned: jm.assignedSandboxes.length, estimation: jm.isEstimation };\n });\n console.debug(jobGrouper);\n }\n }\n finally\n {\n this.isFetchingNewWork = false;\n }\n}\n\n/**\n * @callback cbNext\n * @returns {Slice}\n */\n\n/** \n * Factory function which instantiates a JobSelectionCursor. A JobSelectionCursor\n * steps the order that job slices should be selected for execution in the supervisor,\n * given the current state of the supervisor and the availability of jobs when the\n * inventory was snapshot. The entire slice scheduling algorithm is represented by\n * this cursor.\n *\n * The basic idea behind the scheduling of slices in this implementation is to keep as\n * many slices from different jobs running as possible, so as to reduce the likelihood\n * of resource contention between sandboxes.\n *\n * Slices are scheduled in here based on the following ruleset:\n * - pick a slice from the longest job that isn't running\n * - choose a slice from the remaining jobs, in order for shortest to longest slice time\n * - if there are any jobs which are nearly finished, every other slice comes from one\n * of these jobs, selected randomly????? <-- NYI. XXXpfr @todo Think about implementing...\n * - jobs which have slicePriority set by the task-distributor may be chosen in place\n * of slices in the above algorith. Jobs with a slicePriority closer to 1 are more likely\n * to exhibit this behaviour.\n * @param {JobManager[]} jobManagerInventory\n * @returns { { next: cbNext } }\n */\nSupervisor.prototype.makeJobSelectionCursor = function Supervisor$JobSelectionCursor (jobManagerInventory)\n{\n /* Variables in this scope function as state information for next() */\n var jobManIdx = 0;\n /** @type {JobManager[]} - All job managers that are ready that have at least one ready slice. */\n var jobManList;\n /** @type {JobManager[]} - All jobManList elements that correspond to preferred jobs. */\n var prefJobManList;\n /* Number of working sandboxes allowed for a given job. */\n var concurrency = 1;\n const that = this;\n \n function seed (_concurrency)\n {\n function countSandboxes(jobAddress)\n {\n const jobManager = that.jobMap[jobAddress];\n if (!jobManager) throw new Error(`Lost track of job manager for address '${jobAddress}'`);\n return jobManager.workingSlices.length;\n }\n \n // Reset.\n jobManIdx = 0;\n\n /* Populate jobManList with jobs which are ready and have at least one slice which is ready. */\n jobManList = jobManagerInventory.filter((jobMan) => jobMan.ready && jobMan.readySlices.length > 0);\n\n /* Populate jobManList with jobManagers whose # of working slices is less than _concurrency. */\n jobManList = jobManList.filter((jobMan) => countSandboxes(jobMan.address) < _concurrency);\n\n /* Increasing sort. */\n jobManList.sort((a,b) => a.estimateWallMs - b.estimateWallMs);\n\n /* Populate prefJobManList with jobs from jobManList which also have a slicePreference set. */\n prefJobManList = jobManList.filter(jobMan => jobMan.hasOwnProperty('slicePreference') );\n }\n\n /**\n * Takes slices off the ready list, marks it reserved and increments workingSandboxCoun,\n * because the slice will soon be working.\n * @param {JobManager} jobMan \n * @returns {Slice}\n */\n function reserveSlice (jobMan)\n {\n const _readySlices = jobMan.readySlices;\n if (_readySlices.length > 0)\n {\n const slice = _readySlices[0];\n slice.markAsReserved();\n return slice;\n }\n return null;\n }\n\n /**\n * Each invocation of next() identifies one slice to run, or returns false if none can run.\n * @returns {Slice}\n */\n function next ()\n {\n /* Adjust order to schedule the heaviest job's first slice asap. */\n jobManList.unshift(jobManList.pop());\n\n let workingSliceCount;\n do\n {\n seed(concurrency);\n\n /* Maybe schedule a prefered job slice based on random chance .*/\n if (prefJobManList.length > 0)\n {\n let prioRan = Math.random();\n let list = prefJobManList.filter(jm => jm['slicePreference'] >= prioRan);\n\n if (list.length > 0)\n {\n const jobMan = list[list.length * Math.random()];\n const slice = reserveSlice(jobMan);\n if (slice)\n return slice;\n }\n }\n\n /* Schedule a slice from next job; jobs are in increasing order of estimated run time. */\n while (jobManIdx < jobManList.length)\n {\n const jobMan = jobManList[jobManIdx];\n jobManIdx++;\n const slice = reserveSlice(jobMan);\n if (slice)\n return slice;\n }\n\n /* If this is reached, we did not schedule a slice with current seed. We need\n * to re-seed to look for newly-available work and sandboxes, ratcheting up the\n * concurrency (max # of each job running) until we find something we can do.\n */\n workingSliceCount = that.workingSliceCount();\n debugging() && console.debug(`job selection - no slice found from ${jobManList.length} jobs for concurrency=${concurrency} and ${workingSliceCount} working sandboxes`);\n } while (jobManList.length > 0 && workingSliceCount < that.maxWorkingSandboxes && concurrency++ < that.maxWorkingSandboxes);\n\n return false; /* Did not find any more work that fits. */\n }\n \n return { next };\n}\n\n/**\n * Handle sandbox.work(...) errors.\n * @param {Sandbox} sandbox \n * @param {Slice} slice \n * @param {Error} error \n * @return {Promise<string>}\n */\nSupervisor.prototype.handleSandboxWorkError = async function Supervisor$handleSandboxWorkError (sandbox, slice, error)\n{\n if (!slice.isWorking) // Sanity. Exception should never fire.\n throw new Error(`handleSandboxWorkError: slice ${slice.identifier} must be WORKING.`);\n\n let logLevel, reason;\n \n if (error instanceof SandboxError)\n {\n logLevel = 'warn';\n reason = error.errorCode;\n // The message and stack properties of error objects are not enumerable,\n // so they have to be copied into a plain object this way\n // @ts-ignore\n error = Object.getOwnPropertyNames(error).reduce((o, p) => {\n o[p] = error[p]; return o;\n }, { message: 'Unexpected worker error' });\n }\n else\n {\n logLevel = 'error';\n if (error)\n reason = `Slice ${slice.sliceNumber} in state ${slice.state} of job ${slice.jobAddress} failed to complete execution with error ${this.checkCode(error)}.`;\n else\n {\n reason = `Slice ${slice.sliceNumber} in state ${slice.state} of job ${slice.jobAddress} failed to complete execution.`;\n error = new Error(reason);\n }\n // This error was unrelated to the work being done, so just return the slice\n // in the promise.catch in JobManager.runSliceOnSandbox .\n assert(slice.result === null);\n }\n \n this.handleFailedSlice(slice, error);\n\n let errorString;\n switch (reason)\n {\n case 'ENOPROGRESS':\n errorString = 'No progress error in sandbox.\\n';\n break;\n case 'ESLICETOOSLOW':\n errorString = 'Slice too slow error in sandbox.\\n';\n break;\n case 'EUNCAUGHT':\n errorString = `Uncaught error in sandbox ${error.message}.\\n`;\n break;\n case 'EFETCH':\n // The status.js processing does not have a case for 'EFETCH' yet.\n errorString = `Could not fetch data: ${error.message}.\\n`;\n break;\n }\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 const displayMaxInfo = slice.jobManager.displayMaxDiagInfo;\n\n const errorObject = {\n jobAddress: truncateAddress(slice.jobAddress, addressTruncationLength),\n sliceNumber: slice.sliceNumber,\n sandbox: sandbox.id,\n jobName: sandbox.public ? sandbox.public.name : 'unnamed',\n };\n if (displayMaxInfo)\n errorObject.stack += '\\n --------------------\\n' + (error.stack.split('\\n').slice(1).join('\\n'));\n\n if (error.name === 'EWORKREJECT')\n {\n reason = 'EWORKREJECT'; // The status.js processing does not have a case for 'EWORKREJECT' yet.\n error.stack = 'Sandbox was terminated by work.reject()';\n await this.handleWorkReject(sandbox, slice, error.message);\n }\n else\n {\n this.returnSlice(slice, reason);\n slice.jobManager.returnSandbox(sandbox);\n }\n\n if (errorString)\n console[logLevel](errorString, errorObject);\n else if (error.name === 'EWORKREJECT')\n console[logLevel](`Slice rejected work: ${error.message}`)\n else\n console[logLevel](`Slice failed: ${error.message}\\n`, errorObject);\n\n return reason;\n}\n\n/**\n * Slice has thrown error during execution:\n * Mark slice as failed, compensate when job is dicrete, emit events.\n * @param {Slice} slice\n * @param {Error} error\n */\nSupervisor.prototype.handleFailedSlice = function Supervisor$handleFailedSlice (slice, error)\n{\n assert(error, 'error must be valid');\n slice.collectResult(error, false);\n\n // If the slice from a job never completes and the job address exists in the ringBufferofJobs,\n // then we remove it to allow for another slice (from the same job) to be obtained by fetchTask\n this.ringBufferofJobs.buf = this.ringBufferofJobs.filter(element => element !== slice.jobAddress);\n\n this.emit('submitSliceFailed', error);\n this.emit('submitFinished');\n}\n\n// _Idx\n//\n// Return slices and sent progress reports to result-submitter-results.\n// Return slices to result-submitter-status which marks the\n// corresponding row in activeSlices to be rescheduled on usually another worker.\n//\n\n/**\n * Bulk-return multiple slices, possibly for assorted jobs.\n * Returns slices to the scheduler to be redistributed.\n * Called in the sandbox terminate handler and purgeAllWork(jobAddress)\n * and stopWork(forceTerminate).\n *\n * @param {Slice[]} slices - The slices to return to the scheduler.\n * @param {string} [reason='unknown'] - Optional reason for the return: 'ENOPROGRESS', 'EUNCAUGHT', 'ESLICETOOSLOW', 'unknown'.\n * @param {boolean} [removeSlices=true] - When true, removes slices from this.sliceInventory .\n * @returns {Promise<*>} - Response from the scheduler.\n */\nSupervisor.prototype.returnSlices = function Supervisor$$returnSlices (slices, reason = 'unknown', removeSlices = true)\n{\n if (!slices || !slices.length) return Promise.resolve();\n debugging('supervisor') && console.debug('Supervisor.returnSlices: Returning slices', slices.map(slice => slice.identifier));\n\n const slicePayload = constructReturnSliceBuckets(slices, reason);\n if (removeSlices) slices.forEach((slice) => slice.jobManager.removeSlice(slice));\n\n try\n {\n return this.resultSubmitter.send('status', {\n worker: this.workerId,\n slices: slicePayload,\n }).catch(error => {\n const errorInfo = slices.map(slice => slice.identifier).sort();\n debuggingError && console.error('Failed to return slice(s)', { errorInfo, error }, 'Will try again on new connection.');\n return this.saveForResubmitToRS('status', { worker: this.workerId, slices: slicePayload });\n });\n }\n catch (error)\n {\n /* resultSubmitter can be null if worker is stopped */\n debuggingError && console.error(`Failed to return slices ${compressSlices(slices)}, no connection to result submitter:`, error);\n }\n}\n\n/** XXXpfr @todo TEMP -- Remove when sup2 replaces sup1 */\nfunction compressSlices(sliceArray)\n{\n const jobSliceMap = toJobMap(sliceArray, slice => slice.sliceNumber);\n return compressJobMap(jobSliceMap, false /* skipFirst*/, addressTruncationLength);\n}\n\n/**\n * Takes a slice and returns it to the scheduler to be redistributed.\n * Usually called when an exception is thrown by sandbox.work(...) .\n * Or when the supervisor tells it to forcibly stop working.\n *\n * @param {Slice} slice - The slice to return to the scheduler.\n * @param {string} [reason] - Optional reason for the return: 'ENOPROGRESS', 'EUNCAUGHT', 'ESLICETOOSLOW', 'unknown'.\n * @returns {Promise<*>} - Response from the scheduler.\n */\nSupervisor.prototype.returnSlice = function Supervisor$$returnSlice (slice, reason)\n{\n assert(slice.sliceNumber > 0 && slice.jobManager);\n debugging() && console.debug(`Supervisor.returnSlice: Returning slice ${slice.identifier} with reason ${reason}.`);\n\n if (!this.resultSubmitter)\n this.connectTo('resultSubmitter');\n\n try\n {\n slice.jobManager.removeSlice(slice);\n const payload = slice.getReturnMessagePayload(this.workerId, reason);\n return this.resultSubmitter.send('status', payload)\n .catch(error => {\n debuggingError && console.error('Failed to return slice', {\n sliceNumber: slice.sliceNumber,\n jobAddress: slice.jobAddress,\n status: slice.state.valueOf(),\n error,\n }, 'Will try again on a new connection.');\n return this.saveForResubmitToRS('status', payload);\n });\n }\n catch (error)\n {\n /* resultSubmitter can be null if worker is stopped */\n debuggingError && console.error(`Failed to return slice ${slice.identifier}, no connection to result submitter:`, error);\n }\n}\n\n/**\n * Send beacon to status.js for 'progress' and sliceStatus.scheduled.\n *\n * Run in an interval created in @constructor .\n * @returns {Promise<*>}\n */\nSupervisor.prototype.emitProgressReport = function emitProgressReport () \n{\n const slices = constructSliceBuckets( this.readySlices(), sliceStatus.scheduled );\n constructSliceBuckets( this.workingSlices(), 'progress', slices );\n\n debugging('supervisor') && console.debug('emitProgressReport:', stringify(slices));\n\n if (slices.length)\n {\n const progressReportPayload = {\n worker: this.workerId,\n slices,\n };\n\n try\n {\n return this.resultSubmitter.send('status', progressReportPayload)\n .catch(error => {\n debuggingError && console.error('479: Failed to send status beacon update:', error/*.message*/);\n return this.saveForResubmitToRS('status', progressReportPayload);\n });\n }\n catch (error) \n {\n /* resultSubmitter can be null if worker is stopped */\n debuggingError && console.error(`Failed to emit progress report, no connection to result submitter:`, error);\n }\n }\n}\n\n/**\n * Add a slice to the slice buckets being built. If a sliceBucket already exists for the\n * job-status-authMessage tuple, then the slice will be added to that, otherwise a new\n * sliceBucket will be added to the buckets.\n *\n * @param {Slice[]} slices - The slices.\n * @param {String} status - Status update, eg. progress or scheduled.\n * @param {Object[]} [sliceBuckets] - Slice buckets being built. Will be mutated in place.\n * @returns {Object[]} - mutated sliceBuckets array\n */\nfunction constructSliceBuckets (slices, status, sliceBuckets)\n{\n const jobMap = {};\n for (const slice of slices)\n {\n assert(slice.sliceNumber > 0 );\n if (!jobMap[slice.jobAddress]) jobMap[slice.jobAddress] = [];\n\n // Try to find a sliceBucket in the buckets which matches the job, status, and auth message.\n let sliceBucket = jobMap[slice.jobAddress].find(desc => {\n return desc.status === status\n && desc.authorizationMessage === slice.authorizationMessage;\n });\n\n if (!sliceBucket) jobMap[slice.jobAddress].push(slice.getMessage(status));\n else sliceBucket.sliceNumbers.push(slice.sliceNumber);\n }\n\n if (!sliceBuckets) return Object.values(jobMap);\n sliceBuckets.push(...Object.values(jobMap));\n return sliceBuckets;\n}\n \n/**\n * Add a slice to the returnSlice bucket being built. If a sliceBucket already exists for the\n * job-isEstimation-authMessage-reason tuple, then the slice will be added to that, otherwise a new\n * sliceBucket will be added to the buckets.\n *\n * @param {Slice[]} slices - The slices.\n * @param {String} [reason] - Optional reason to further characterize status; e.g. 'ENOPROGRESS', 'EUNCAUGHT', 'ESLICETOOSLOW', 'unknown'.\n * @param {Object[]} [sliceBuckets] - Optional slice buckets being built. Will be mutated in place.\n * @returns {Object[]} - mutated sliceBuckets array\n */\nfunction constructReturnSliceBuckets (slices, reason, sliceBuckets)\n{\n const jobMap = {};\n for (const slice of slices)\n {\n assert(slice.sliceNumber > 0 );\n if (!jobMap[slice.jobAddress]) jobMap[slice.jobAddress] = [];\n \n // Try to find a sliceBucket in the buckets which matches the job, estimation status, reason, and auth message.\n let sliceBucket = jobMap[slice.jobAddress].find(desc => {\n return desc.isEstimationSlice === slice.isEstimation\n && desc.authorizationMessage === slice.authorizationMessage\n && desc.reason === reason;\n });\n\n if (!sliceBucket) \n jobMap[slice.jobAddress].push(slice.getMessage('return', { isEstimationSlice: slice.isEstimation, reason }));\n else sliceBucket.sliceNumbers.push(slice.sliceNumber);\n }\n\n if (!sliceBuckets) return Object.values(jobMap);\n sliceBuckets.push(...Object.values(jobMap));\n return sliceBuckets;\n}\n \n// _Idx\n//\n// Task Distributor (TD): requestTask (Rq) support -- communication with TD.\n//\n\n/**\n * XXXpfr @todo Needs Work\n * For a given job, the scheduler stores an EMA approximation of average slice completion time in\n * jobPerfData.sliceCPUTime (and jobPerfData.sliceGPUTime, but we don't do the GPU analysis yet.)\n * However, each worker also tracks the same information and the ratio of local-info to scheduler-info\n * is returned by this.conversionQuantum so we can tell the task distributor how much work to return\n * from requestTask so that the work actually takes 5 minutes to complete when using all the worker sandboxes.\n * Note: \n * We average the completion times over the current jobs.\n * Define completion time in terms of sliceC(G)PUTime and sliceC(G)PUDensity\n * completion-time = (sliceCGPUTime + sliceCGPUTime) / ( sliceCPUDensity + sliceGPUDensity);\n * The local completion time is an EMA approximation of local completion-time as computed by Supervisor.recordResult.\n * The scheduler completion-time is computed directly from the corresponding row in jobPerfData.\n */\nSupervisor.prototype.conversionQuantum = function Supervisor$conversionQuantum()\n{\n let globalSpeed = 0, localSpeed = 0;\n for (const jobMan of this.jobManagerInventory)\n {\n const _globalTime = jobMan.globalTime;\n const _localTime = jobMan.statistics.ema;\n if (_globalTime > 0 && _localTime > 0)\n {\n //console.debug('conversionQuantum: local', _localTime, 'global', _globalTime);\n globalSpeed += _globalTime;\n localSpeed += _localTime;\n }\n }\n const conversion = globalSpeed > 0 ? localSpeed / globalSpeed : 1;\n return Math.min(Math.max(conversion, 0.2), 5.0); // Truncate if conversion is too bizarre.\n}\n\n/**\n * Remove all unreferenced jobs in this.jobManagerInventory and this.moduleCache.\n * Since job-managers are inserted into this.jobManagerInventory with a push, the job managers at the beginning are oldest.\n * Only delete #deleteCount of the oldest job-managers:\n * let deleteCount = this.jobManagerInventory.length - cachedJobsThreshold;\n * Edit cachedJobsThreshold to adjust the cache cleanup threshold.\n * @param {object[]} [newJobKeys=[]] - Jobs that should not be removed from this.jobManagerInventory and this.moduleCache.\n */\nSupervisor.prototype.clearUnusedJobManagersAndModuleCache = function Supervisor$clearUnusedJobManagersAndModuleCache(newJobKeys=[])\n{\n let deleteCount = this.jobManagerInventory.length - this.cachedJobsThreshold;\n if (deleteCount > 0)\n {\n selectiveDebugging && console.debug(`Supervisor.clearUnusedJobManagersAndModuleCache START: deleteCount ${deleteCount}/${this.jobManagerInventory.length}/${this.cachedJobsThreshold}.`);\n const jobMap = {};\n newJobKeys.forEach(jobAddress => { jobMap[jobAddress] = 1; });\n for (const jobManager of this.jobManagerInventory)\n {\n if (!jobMap[jobManager.address])\n {\n const sliceInventory = jobManager.sliceInventory.filter(slice => slice.isActive || slice.isQueued);\n if (sliceInventory.length < 1)\n {\n this.purgeJob(jobManager);\n if (--deleteCount < 1)\n break;\n }\n }\n }\n selectiveDebugging && console.debug(`Supervisor.clearUnusedJobManagersAndModuleCache FINISH: deleteCount ${deleteCount}/${this.jobManagerInventory.length}/${this.cachedJobsThreshold}.`);\n }\n}\n\n/**\n * Ask the scheduler (task distributor) for work.\n * @param {number} [unusedSandboxSlots]\n * @param {object[]} [jobs=[]] \n */\nSupervisor.prototype.requestTask = function Supervisor$requestTask (unusedSandboxSlots, jobs = [])\n{\n if (!this.isReady() || this.isFetchingNewWork)\n return Promise.resolve();\n\n if(!unusedSandboxSlots) unusedSandboxSlots = this.unusedSandboxSlots();\n if (unusedSandboxSlots < 1)\n {\n debugging('supervisor') && console.debug('requestTask: There are no unused sandbox slots.');\n return Promise.resolve();\n }\n\n // Refresh connections.\n this.instantiateAllConnections();\n\n // We prune for over this.maxTotalSandboxes about every 15 seconds, or when must prune level is reached.\n if (this.sandboxCount() > this.mustPruneSandboxLevel || Date.now() > this.lastPrune + this.pruneFrequency)\n {\n this.lastPrune = Date.now();\n this.pruneSandboxes();\n }\n\n try\n {\n this.isFetchingNewWork = true;\n const numCPUSlotToFill = this.numberOfAvailableSandboxSlots(unusedSandboxSlots);\n if (numCPUSlotToFill < 1)\n {\n //debugging() && console.debug('Predicted workload too high; not fetching additional work yet'); <-- Save Wes' msg...\n debugging() && console.debug('Supervisor.requestTask: We have enough, so start executing some slices.');\n return this.roundRobinSlices(); // roundRobinSlices guarantees this.isFetchingNewWork === false\n }\n\n /** XXXpfr @todo Get together with Wes to figure this out. */\n //let predictedLoad = this.predictLoad(Date.now() + ms(this.tuning.prefetchInterval)).load;\n\n const request = {\n numCores: numCPUSlotToFill,\n coreStats: this.getStatisticsCPU(),\n numGPUs: this.numGPU,\n //targetLoad: this.targetLoad.subtract(predictedLoad), /** XXXpfr @todo Get together with Wes to figure this out. */\n conversionQuantum: this.conversionQuantum(),\n capabilities: this.capabilities,\n paymentAddress: this.paymentAddress,\n jobAddresses: jobs.concat(this.options.jobAddresses || []), // When set, only fetches slices for these jobs.\n localExec: this.options.localExec,\n workerComputeGroups: this.generateWorkerComputeGroups(),\n minimumWage: workerTuning.minimumWage || this.options.minimumWage,\n loadedJobs: this.jobManagerInventory.map(jobMan => jobMan.address),\n readyJobs: this.jobManagerInventory.filter(jobMan => jobMan.ready).map(jobMan => jobMan.address),\n previouslyWorkedJobs: this.ringBufferofJobs.buf, // Only discrete jobs.\n rejectedJobs: this.rejectedJobs,\n };\n // Workers should be part of the public compute group by default.\n if (!booley(workerTuning.leavePublicGroup) && !booley(this.options.leavePublicGroup))\n request.workerComputeGroups.push(constants.computeGroups.public);\n\n // Call Task Distributor and handle response with this.addTaskToWorkload.\n this.fetchTask(request, (response) => this.addTaskToWorkload(request, response));\n }\n catch (error)\n {\n // Paranoid double-checking we don't accidently leave a live this.isFetchingNewWork.\n this.isFetchingNewWork = false;\n throw error;\n }\n}\n\n/** Gets the logical and physical number of cores and also the total number of sandboxes the worker is allowed to run. */\nSupervisor.prototype.getStatisticsCPU = function Supervisor$getStatisticsCPU ()\n{\n if (DCP_ENV.isBrowserPlatform)\n {\n return {\n worker: this.workerId,\n lCores: window.navigator.hardwareConcurrency,\n pCores: workerTuning.pCores || window.navigator.hardwareConcurrency,\n sandbox: this.maxWorkingSandboxes,\n }\n }\n\n return {\n worker: this.workerId,\n lCores: requireNative('os').cpus().length,\n pCores: requireNative('physical-cpu-count'),\n sandbox: this.maxWorkingSandboxes,\n }\n}\n\n/**\n * Callback for fetchTask.\n * @param {object} request \n * @param {object} response\n */\nSupervisor.prototype.addTaskToWorkload = function Supervisor$addTaskToWorkload (request, response)\n{\n try\n {\n const payload = response.payload;\n if (!response.success)\n {\n debugging() && console.debug('Task fetch failure; request=', request);\n debugging() && console.debug('Task fetch failure; response=', payload);\n // Only report errors when in 'ready' state.\n if (this.isReady()) throw new DCPError('Unable to fetch task for worker', payload);\n else return;\n }\n\n const sliceCount = payload.body.task.length || 0;\n if (sliceCount < 1)\n {\n if (selectiveDebugging2 && (this.lastTime + 7000 < Date.now()))\n {\n this.lastTime = Date.now();\n // Display the state of every slice.\n if (displaySliceState)\n {\n /** @type {JobManager} */\n const jm = this.jobManagerInventory.top();\n jm.dumpSlices(false /*details*/);\n }\n // Display completed results so far.\n if (displayCompletedResults && this.queuedSliceCount() < 1)\n {\n const values = Object.values(this.resultMap);\n if (values.length > 0)\n {\n values.forEach((descriptor) => descriptor.slices.sort((x,y) => x-y))\n console.debug(`Recorded results: job managers ${values.length}:`, this.resultMap);\n }\n }\n }\n this.emit('fetchedTask', { jobs: 0, slices: sliceCount });\n // There may be an extra slice to process.\n // roundRobinSlices guarantees this.isFetchingNewWork === false;\n return this.roundRobinSlices();\n }\n\n /**\n * payload structure: { owner: this.address, signature: signature, auth: messageLightWeight, body: messageBody };\n * messageLightWeight: { workerId: worker, jobSlices, schedulerId, jobCommissions }\n * messageBody: { newJobs: await-getNewJobsForTask(dbScheduler, task, request), task }\n */\n const { body, ...authorizationMessage } = payload;\n /** @type {{ newJobs: object, task: SliceMessage[] }} */\n const { newJobs, task } = body;\n assert(newJobs); // It should not be possible to have !newJobs -- we throw on !success.\n const newJobKeys = Object.keys(newJobs);\n const jobCount = newJobKeys.length;\n\n /*\n * Ensure all jobs received from the scheduler (task distributor) are:\n * 1. If we have specified specific jobs the worker may work on, the received jobs are in the specified job list\n * 2. If we are in localExec, at most 1 unique job type was received (since localExec workers are designated for only one job)\n * If the received jobs are not within these parameters, stop the worker since the scheduler cannot be trusted at that point.\n */\n if (request.jobAddresses.length > 0 && !newJobKeys.every((ele) => request.jobAddresses.includes(ele))\n || request.localExec && jobCount > 1)\n {\n this.error(\"Worker received slices it shouldn't have. Rejecting the work and stopping.\");\n process.exit(1);\n }\n\n selectiveDebugging && console.debug(`Supervisor.addTaskToWorkload: task: ${task.length}/${request.numCores}/${this.maxWorkingSandboxes}, conversion: ${request.conversionQuantum}, jobs: ${jobCount}, authSlices: ${compressJobMap(authorizationMessage.auth.authSlices, true /* skipFirst*/, addressTruncationLength /* digits*/)}`);\n\n // Clear out job managers w/o any queued slices,\n // and remove corresponding job references from module cache.\n // When a cached module no longer has any job references it is removed from the cache.\n this.clearUnusedJobManagersAndModuleCache(newJobKeys);\n\n // this.jobMap: job.address --> jobManager\n /** @type {Object.<Address, JobManager>} */\n this.jobMap = {};\n this.jobManagerInventory.forEach(jobManager => {\n this.jobMap[jobManager.address] = jobManager;\n });\n\n /** @type {Object.<Address, SliceMessage[]>} */\n const jobSliceMap = {};\n task.forEach((element) => {\n const address = String(element.jobAddress);\n if (!jobSliceMap[address]) jobSliceMap[address] = [element];\n else jobSliceMap[address].push(element);\n });\n\n debugging('supervisor') && console.debug('requestTask: slices, newJobs and jobMap', task.length, Object.keys(newJobs), Object.keys(this.jobMap));\n\n // Populate the job managers with slices, creating new job managers when necessary.\n // Set up discrete job ring buffer.\n for (const [jobAddress, jobEl] of Object.entries(newJobs))\n {\n if (this.jobMap.hasOwnProperty(jobAddress))\n {\n /** @type {JobManager} */\n const jm = this.jobMap[jobAddress];\n jm.update(jobEl, jobSliceMap[jobAddress], authorizationMessage);\n }\n else\n {\n // Add the slice messages to the job manager ctor, so that slice construction is after job manager is ready.\n const jobManager = new JobManager(this, jobEl, jobSliceMap[jobAddress], authorizationMessage);\n this.jobMap[jobAddress] = jobManager;\n this.jobManagerInventory.push(jobManager);\n\n // Populate the ring buffer based on job's discrete property.\n assert(jobEl.requirements);\n if (jobEl.requirements.discrete && this.ringBufferofJobs.find(address => address === jobEl.address) === undefined)\n this.ringBufferofJobs.push(jobEl.address);\n }\n }\n\n /**\n * The requestTask event fires when the supervisor has finished trying to\n * fetch work from the scheduler (task distributor). The data emitted is the\n * number of jobs and new slices to work on in the fetched task.\n *\n * @event Supervisor#requestTask\n * @type {object}\n */\n this.emit('fetchedTask', { jobs: jobCount, slices: sliceCount });\n\n // Start working on the slices.\n setImmediate(() => this.roundRobinSlices());\n }\n catch (error)\n {\n this.isFetchingNewWork = false; // Paranoid double checking that we don't leave this.isFetchingNewWork live.\n this.emit('fetchTaskFailed', error);\n debuggingError && console.error('Supervisor.requestTask failed!', error);\n }\n}\n\n/**\n * Returns the number of unused sandbox slots to fill -- sent to requestTask.\n * @returns {number}\n */\nSupervisor.prototype.numberOfAvailableSandboxSlots = function Supervisor$numberOfAvailableSandboxSlots(unusedSandboxSlots)\n{\n const _readySlices = this.readySlices();\n let numCores;\n if (this.options.priorityOnly && this.options.jobAddresses.length === 0)\n numCores = 0;\n else if (_readySlices.length > 1) // We have slices ready, no need to fetch.\n numCores = 0;\n else\n {\n // There are almost no ready slices (there may be 0 or 1), fetch a full task.\n // The task is full, in the sense that it will contain slices whose\n // aggregate execution time is this.maxWorkingSandboxes * 5-minutes.\n // However, there can only be unusedSandboxSlots # of long slices.\n // Thus we need to know whether the last slice in this.readySlices() is long or not.\n // (A long slice has estimated execution time >= 5-minutes on an average worker.)\n const longSliceCount = (_readySlices.length > 0 && _readySlices[0].isLong) ? 1 : 0;\n numCores = unusedSandboxSlots - longSliceCount;\n debugging('supervisor') && console.debug('numberOfAvailableSandboxSlots', numCores, unusedSandboxSlots, longSliceCount);\n }\n return numCores;\n}\n\n/**\n * @callback cbAddTaskToWorkload\n * @param {Response} response\n * @returns {Promise<void>}\n */\n\n/**\n * Call to fetch new slices from task distributor.\n * @param {*} request\n * @param {cbAddTaskToWorkload} addTaskToWorkload\n * @returns {Promise<void>}\n */\nSupervisor.prototype.fetchTask = async function Supervisor$fetchTask (request, addTaskToWorkload)\n{\n // Fetch a new task if we have insufficient slices queued, then start workers\n assert(this.isFetchingNewWork);\n\n this.instantiateAllConnections();\n\n // Top up sandboxes when necessary.\n const moreSandboxes = this.maxWorkingSandboxes - this.sandboxCount();\n if (moreSandboxes > 0)\n {\n await this.carefullyCreateSandboxes(moreSandboxes)\n .then(() => this.checkCapabilities()); /** XXXpfr @todo Do we need to check capabilities again? */\n }\n\n const fetchTimeout = setTimeout(() => {\n this.isFetchingNewWork = false;\n this.emit('warning', 'Fetch exceeded timeout, will reconnect at next watchdog interval');\n safeClose(this.taskDistributor, 'Fetch timed out', Math.random() > 0.5).catch(error => {\n this.error('Failed to close task-distributor connection', error);\n });\n safeClose(this.resultSubmitter, 'Fetch timed out', Math.random() > 0.5).catch(error => {\n this.error('Failed to close result-submitter connection', error);\n });\n this.instantiateAllConnections();\n }, 3 * 60 * 1000); // Max out at 3 minutes to fetch.\n\n // Ensure result submitter and task distributor connections before fetching tasks.\n try\n {\n await this.taskDistributor.keepalive();\n await this.resultSubmitter.keepalive();\n await this.taskDistributor.keepalive();\n }\n catch (e)\n {\n this.isFetchingNewWork = false;\n this.error('Failed to connect to result submitter, refusing to fetch slices. Will try again at next fetch cycle.', e);\n clearTimeout(fetchTimeout);\n safeClose(this.taskDistributor, 'Failed to connect to task-distributor', true).catch(error => {\n this.error('Failed to close task-distributor connection', error);\n });\n safeClose(this.resultSubmitter, 'Failed to connect to result-submitter', true).catch(error => {\n this.error('Failed to close result-submitter connection', error);\n });\n return Promise.resolve();\n }\n\n this.emit('fetchingTask');\n\n if (!this.taskDistributor) return\n return this.taskDistributor.send('requestTask', request)\n .then((response) => {\n addTaskToWorkload(response);\n })\n .catch((error) => {\n this.isFetchingNewWork = false; // Redundant.\n this.emit('fetchTaskFailed', error);\n this.error('Unable to request task from scheduler. Will try again on a new connection.', error);\n safeClose(this.taskDistributor, 'Failed to request task.', true);\n })\n .finally(() => {\n this.isFetchingNewWork = false;\n clearTimeout(fetchTimeout);\n });\n}\n\n/**\n * Generate the workerComputeGroups property of the requestTask message. \n * \n * Concatenate the compute groups object from dcpConfig with the list of compute groups\n * from the supervisor, and remove the public group if accidentally present. Finally,\n * we transform joinSecrets/joinHash into joinHashHash for secure transmission.\n *\n * @note computeGroup objects with joinSecrets are mutated to record their hashes. This\n * affects the supervisor options and dcpConfig. Re-adding a joinSecret property\n * to one of these will cause the hash to be recomputed.\n */\nSupervisor.prototype.generateWorkerComputeGroups = function Supervisor$generateWorkerComputeGroups ()\n{\n let computeGroups = Object.values(workerTuning.computeGroups || {});\n if (this.options.computeGroups)\n computeGroups = computeGroups.concat(this.options.computeGroups);\n computeGroups = computeGroups.filter(group => group.id !== constants.computeGroups.public.id);\n const hashedComputeGroups = [];\n for (const group of computeGroups)\n {\n const groupCopy = Object.assign({}, group);\n if ((group.joinSecret || group.joinHash) && (!group.joinHashHash || this.lastDcpsid !== this.taskDistributor.dcpsid))\n {\n let joinHash;\n if (group.joinHash)\n joinHash = group.joinHash.replace(/\\s+/g, ''); // strip whitespace\n else\n joinHash = calculateJoinHash(groupCopy);\n\n groupCopy.joinHashHash = hash.calculate(hash.eh1, joinHash, this.taskDistributor.dcpsid);\n delete groupCopy.joinSecret;\n delete groupCopy.joinHash;\n debugging('computeGroups') && console.debug(`Calculated joinHash=${joinHash} for`, groupCopy);\n }\n hashedComputeGroups.push(groupCopy);\n }\n this.lastDcpsid = this.taskDistributor.dcpsid;\n debugging('computeGroups') && console.debug('Requesting ', computeGroups.length, 'non-public groups for session', this.lastDcpsid);\n return hashedComputeGroups;\n}\n\n// _Idx\n//\n// Aggregators from the job managers.\n// Note: Not all functions are used yet.\n//\n/** XXXpfr @todo Figure out which aggregators to keep. */\n\n/**\n * Gather the count of job managers with queuedSlices.\n * @returns {number}\n */\nSupervisor.prototype.activeJobCount = function Supervisor$activeJobCount ()\n{\n let count = 0;\n this.jobManagerInventory.forEach((jobManager) => {\n if (jobManager.queuedSlices.length > 0) count++;\n });\n return count;\n}\n\n/**\n * Gather the ready slices from the job managers.\n * @returns {Slice[]}\n */\nSupervisor.prototype.readySlices = function Supervisor$readySlices ()\n{\n const readySlices = [];\n this.jobManagerInventory.forEach((jobManager) => {\n readySlices.push(...jobManager.readySlices);\n });\n return readySlices;\n}\n\n/**\n * Gather the working slices in the job managers.\n * @returns {Slice[]}\n */\nSupervisor.prototype.workingSlices = function Supervisor$workingSlices ()\n{\n const workingSlices = [];\n this.jobManagerInventory.forEach((jobManager) => {\n workingSlices.push(...jobManager.workingSlices);\n });\n return workingSlices;\n}\n\n/**\n * Gather the count of various kinds of slices over all the job managers.\n * @param {string} predicate - 'all;, 'ready', 'queued', 'reserved', 'working', 'workingOnly'.\n * @returns {number}\n */\nSupervisor.prototype.predicateSliceCount = function Supervisor$predicateSliceCount (predicate)\n{\n let count = 0;\n switch (predicate)\n {\n case 'all':\n this.jobManagerInventory.forEach((jobManager) => {\n count += jobManager.sliceInventory.length;\n });\n break\n case 'ready':\n this.jobManagerInventory.forEach((jobManager) => {\n count += jobManager.readySlices.length;\n });\n break;\n case 'queued':\n this.jobManagerInventory.forEach((jobManager) => {\n count += jobManager.queuedSlices.length;\n });\n break;\n case 'reserved':\n this.jobManagerInventory.forEach((jobManager) => {\n count += jobManager.reservedSlices.length;\n });\n break;\n case 'working': // both working and reserved (viz., soon-to-be-working)\n this.jobManagerInventory.forEach((jobManager) => {\n count += jobManager.workingSlices.length;\n });\n break;\n case 'workingOnly':\n this.jobManagerInventory.forEach((jobManager) => {\n count += jobManager.workingSlicesOnly.length;\n });\n break;\n }\n return count;\n}\n/** @returns {number} */\nSupervisor.prototype.sliceCount = function Supervisor$sliceCount () { return this.predicateSliceCount('all'); }\n/** @returns {number} */\nSupervisor.prototype.readySliceCount = function Supervisor$readySliceCount () { return this.predicateSliceCount('ready'); }\n/** @returns {number} */\nSupervisor.prototype.queuedSliceCount = function Supervisor$queuedSliceCount () { return this.predicateSliceCount('queued'); }\n/** @returns {number} */\nSupervisor.prototype.reservedSliceCount = function Supervisor$reservedSliceCount () { return this.predicateSliceCount('reserved'); }\n/** @returns {number} */\nSupervisor.prototype.workingSliceCount = function Supervisor$workingSliceCount () { return this.predicateSliceCount('working'); }\n/** @returns {number} */\nSupervisor.prototype.workingSliceOnlyCount = function Supervisor$workingSliceOnlyCount () { return this.predicateSliceCount('workingOnly'); }\n\n/**\n * Gather the count of working sandboxes over all the job managers.\n * @returns {number}\n */\nSupervisor.prototype.sandboxCount = function Supervisor$sandboxCount ()\n{\n return this.readiedSandboxes.length + this.sandboxInventory.filter((sandbox) => !sandbox.isTerminated).length;\n}\n\n/**\n * Gather the count of working sandboxes over all the job managers.\n * @returns {number}\n */\nSupervisor.prototype.workingSandboxCount = function Supervisor$workingSandboxCount ()\n{\n return this.sandboxInventory.filter((sandbox) => !sandbox.isTerminated && sandbox.isWorking).length;\n}\n\n// _Idx\n//\n// Sandbox creation and management.\n// \n\n/**\n * Create and start a Sandbox.\n * When this.readiedSandboxes.length > 0, use one of those sandboxes, instead of creating a new one.\n * @param {number} [delayMs=0] - The delay ms when calling sandbox.start(delayMs) .\n * @returns {Promise<Sandbox>}\n */\nSupervisor.prototype.createSandbox = function Supervisor$createSandbox (delayMs = 0)\n{\n const that = this;\n function getReadiedSandbox()\n {\n const sandbox = that.readiedSandboxes.pop();\n that.sandboxInventory.push(sandbox);\n return Promise.resolve(sandbox);\n }\n\n if (this.readiedSandboxes.length > 0)\n return getReadiedSandbox();\n\n // Do not place in this.readiedSandboxes, we'll directly use the return value of createSandbox.\n return this.createNewSandbox(delayMs, true/*putInInventory*/)\n .catch(() => {\n return this.carefullyCreateSandboxes(1)\n .then(() => {\n return getReadiedSandbox();\n });\n });\n}\n \n/**\n * Create and start a Sandbox.\n * Store it in this.readiedSandboxes or this.sandboxInventory according to putInInventory.\n * @param {number} [delayMs=0] - Millisecond delay when calling sandbox.start(delayMs), otherwise return it and use it.\n * @param {boolean} [putInInventory=false]\n * @returns {Promise<Sandbox>}\n */\nSupervisor.prototype.createNewSandbox = function Supervisor$createNewSandbox (delayMs = 0, putInInventory = false)\n{\n const rawSandbox = new Sandbox(this, { ...this.options.sandboxOptions });\n this.hookUpSandboxListeners(rawSandbox);\n return rawSandbox.start(delayMs)\n .then((sandbox) => {\n if (putInInventory) this.sandboxInventory.push(sandbox);\n else this.readiedSandboxes.push(sandbox);\n return sandbox;\n })\n .catch((error) => {\n if (!error) error = new Error('Unknown error creating sandbox.');\n debuggingWarn && console.warn(`Supervisor.createNewSandbox: Failed to start sandbox ${rawSandbox.identifier}.`, error);\n rawSandbox.terminate(false);\n if (error.code === 'ENOWORKER')\n throw new DCPError(\"Cannot use localExec without dcp-worker installed. Use the command 'npm install dcp-worker' to install the neccessary modules.\", 'ENOWORKER');\n throw error;\n });\n}\n\n/**\n * Bulk: create and start sandboxes and save in this.readiedSandboxes.\n * Call this function when there's a chance the evaluator is down.\n * @param {number} count - The number of sandboxes to create.\n * @returns {Promise<void>}\n */\nSupervisor.prototype.carefullyCreateSandboxes = async function Supervisor$carefullyCreateSandboxes (count)\n{\n if (count < 1) return;\n // If the evaluator cannot start (e.g. if the evalServer is not running),\n // then the while loop will keep retrying until the evalServer comes online.\n let retry = 0;\n while (true)\n {\n try\n {\n await this.createNewSandbox();\n if (count > 1)\n this.createSandboxes(count - 1);\n }\n catch (error)\n {\n if (error.code === 'ENOWORKER') throw error;\n // Now assume the evaluator is down and keep retrying.\n /** XXXpfr @todo Need better indicator that evaluator is down. */\n if ((retry++ % 6) === 0)\n this.error(`Failed to ready sandboxes; will keep retrying: ${this.checkCode(error)}`);\n await a$sleepMs(1000 * Math.max(5, retry));\n }\n }\n}\n\n/**\n * Bulk: create and start sandboxes and save in this.readiedSandboxes.\n * @param {number} count - The number of sandboxes to create.\n * @returns {Promise<void>}\n */\nSupervisor.prototype.createSandboxes = async function Supervisor$createSandboxes (count)\n{\n assert(count > 0);\n const promises = [], errors = [];\n for (let k = 0; k < count; k++)\n {\n promises.push(\n this.createNewSandbox(k === 0 ? 0: this.delayMs())\n .catch((error) => errors.push(this.checkCode(error))));\n }\n\n await Promise.all(promises);\n\n if (errors.length)\n this.emit('warning', `Failed to ready ${errors.length} of ${count} sandboxes: ${errors.map(err => err.message)}`);\n\n // Sort so that pop() will return sandboxes in increasing order.\n this.readiedSandboxes.sort((x,y) => y.id - x.id);\n\n debugging('supervisor') && console.debug(`createSandboxes: Created ${count-errors.length} sandboxes.`, this.readiedSandboxes.map(s => s.id));\n}\n\n/**\n * For a given sandbox, hook up all the Sandbox listeners.\n * @param {Sandbox} sandbox \n */\nSupervisor.prototype.hookUpSandboxListeners = function hookUpSandboxListeners (sandbox) \n{\n sandbox.addListener('ready', () => this.emit('sandboxReady', sandbox));\n\n sandbox.addListener('start', () => {\n this.emit('sandboxStart', sandbox);\n\n if (sandbox.slice)\n {\n try\n {\n const statusPayload = sandbox.slice.getMessagePayload(this.workerId, 'begin');\n return this.resultSubmitter.send('status', statusPayload).catch((error) => {\n debuggingError && console.error(`Error sending 'status' for slice ${sandbox.slice.identifier}:\\n\\t${error}\\n\\tWill try again on a new connection`);\n return this.saveForResubmitToRS('status', statusPayload);\n });\n } \n catch (error)\n {\n /* resultSubmitterConnection can be null if worker is stopped */\n debuggingError && console.error(`Failed to send 'begin' status for slice ${sandbox.slice.identifier}, no connection to result submitter`, error);\n }\n }\n });\n\n sandbox.addListener('workEmit', ({ eventName, payload }) => {\n // Need to check if the sandbox hasn't been assigned a slice yet.\n if (!sandbox.slice)\n this.error(`Sandbox not assigned a slice before sending workEmit message to scheduler.\\n\\t'workEmit' event originates from '${eventName}' event`);\n else\n {\n const slice = sandbox.slice;\n // Sometimes a sliceNumber===0 workEmit comes in before the client bundle is properly loaded.\n // Also happens with minor dcp-client version mismatches.\n // sliceNumber===0 <==> authorizationMessage undefined...\n if (!slice.authorizationMessage)\n this.emit('warning', `workEmit: missing authorization message for slice ${slice.identifier}`);\n else if (this.eventRouter) // No reason to emit if event router is closed.\n {\n const workEmitPayload = {\n eventName,\n payload,\n job: slice.jobAddress,\n slice: slice.sliceNumber,\n worker: this.workerId,\n authorizationMessage : slice.authorizationMessage,\n };\n\n const workEmitPromise = this.eventRouter.send('workEmit', workEmitPayload).catch(error => {\n debuggingWarn && console.warn(`workEmit: Unable to send ${eventName} for slice ${slice.identifier}: ${error.message}.\\n\\tTrying again on a new connection.`);\n this.eventRouterMessageQueue.push({ operation: 'workEmit', data: workEmitPayload })\n safeClose(this.eventRouter); // stopWork could slip-in during eventRouter.send\n if (this.debugBuild) this.error('workEmit error:', error);\n });\n\n if (this.debugBuild)\n {\n workEmitPromise.then(result => {\n if (!result) this.emit('warning', `workEmit: Event router did not accept event ${eventName}`);\n });\n }\n }\n }\n });\n\n sandbox.on('rejectedWorkMetrics', (data) => {\n // If the slice already has rejectedTimeReport, add this data to it. If not, assign this data to slices rejectedTimeReport property\n if (sandbox.slice) \n {\n if (!sandbox.slice.rejectedTimeReport) sandbox.slice.rejectedTimeReport = data.timeReport;\n else \n {\n ['total', 'CPU', 'webGL'].forEach((key) => {\n if (data.timeReport[key]) sandbox.slice.rejectedTimeReport[key] += data.timeReport[key];\n })\n }\n }\n });\n\n // If the sandbox terminated and we are not shutting down, then we should return all work which is\n // currently not being computed if all sandboxes are dead and the attempt to create a new one fails.\n sandbox.on('terminated', async () => {\n let nonTerminatedSandboxes = this.sandboxInventory.filter(sbx => !sbx.isTerminated);\n if (nonTerminatedSandboxes.length === 0 && this.worker.working)\n {\n debugging('supervisor') && console.debug(`hookUpSandboxListeners: Try to create 1 sandbox in the sandbox-terminated-handler.`);\n const _sandbox = await this.createNewSandbox()\n .catch((error) => {\n debugging('supervisor') && console.warn('Failed to replace terminated sandbox; evalserver may be gone.', error.message);\n error.message = 'Failed to replace terminated sandbox: ' + error.message;\n this.emit('warning', error);\n });\n\n // If we cannot create a new sandbox, that probably means we're on a screensaver worker\n // and the screensaver is down. So return the slices to the scheduler.\n if (!_sandbox) this.screenSaverDestroy();\n }\n });\n\n sandbox.on('error', (error) => this.emit('error', error));\n sandbox.on('warning', (warning) => this.emit('warning', warning));\n}\n\n/**\n * Terminate extra sandboxes over the limit: this.maxTotalSandboxes.\n * First terminate assigned sandboxes which are unlikely to be used with the current ready slices.\n * Then terminate the unassigned sandboxes: this.readiedSandboxes.\n * (There should be no readied sandboxes at this point.)\n * Then round-robin prune 1 assigned sandbox from each jobmanager.\n * XXXpfr @todo Prioritize sandboxes that we wish to keep.\n * E.g. When a sandbox is especially expensive to assign.\n */\nSupervisor.prototype.pruneSandboxes = function Supervisor$pruneSandboxes () \n{\n let pruneCount = this.sandboxCount() - this.maxTotalSandboxes;\n if (pruneCount <= 0) return;\n selectiveDebugging && console.debug(`Supervisor.pruneSandboxes START: pruneCount ${pruneCount}/${this.sandboxCount()}/${this.maxTotalSandboxes}.`);\n // Only prune the extras: jm.assignedSandboxes.length > jm.queuedSlices.length .\n // Round-robin prune 1 extra assigned sandbox from each jobmanager.\n const readyJobManagers = this.jobManagerInventory.filter(jm => jm.ready);\n while (true)\n {\n const _pruneCount = pruneCount;\n for (const jm of readyJobManagers)\n {\n if (jm.pruneExtraAssignedSandbox())\n {\n if (--pruneCount < 1)\n {\n selectiveDebugging && console.debug(`Supervisor.pruneSandboxes FINISH: unpruned ${pruneCount}/${this.sandboxCount()}/${this.maxTotalSandboxes}.`);\n return;\n }\n }\n }\n if (pruneCount === _pruneCount)\n break;\n }\n assert(pruneCount > 0);\n // Prune the excess non-assigned sandboxes -- we should never hit this.\n if (this.readiedSandboxes.length > 0)\n {\n const toPrune = this.readiedSandboxes.slice(0, pruneCount);\n this.readiedSandboxes = this.readiedSandboxes.slice(pruneCount);\n toPrune.forEach(sandbox => sandbox.terminate(false));\n pruneCount -= toPrune.length;\n if (pruneCount < 1)\n {\n selectiveDebugging && console.debug(`Supervisor.pruneSandboxes FINISH: unpruned ${pruneCount}/${this.sandboxCount()}/${this.maxTotalSandboxes}.`);\n return;\n }\n }\n // Round-robin prune 1 assigned sandbox from each jobmanager.\n while (true)\n {\n const _pruneCount = pruneCount;\n for (const jm of readyJobManagers)\n {\n if (jm.pruneAssignedSandbox())\n {\n if (--pruneCount < 1)\n {\n selectiveDebugging && console.debug(`Supervisor.pruneSandboxes FINISH: unpruned ${pruneCount}/${this.sandboxCount()}/${this.maxTotalSandboxes}.`);\n return;\n }\n }\n }\n if (pruneCount === _pruneCount)\n break;\n }\n this.sandboxInventory = this.sandboxInventory.filter((sandbox) => !sandbox.isTerminated);\n selectiveDebugging && console.debug(`Supervisor.pruneSandboxes FINISH: unpruned ${pruneCount}/${this.sandboxCount()}/${this.maxTotalSandboxes}.`);\n}\n\n// _Idx\n//\n// Result-submitter-result support functions.\n// Send in the results!!!\n//\n\n/**\n * Submits the slice results to the result-submitter service.\n * Then remove the slice from the its job manager.\n *\n * @param {Slice} slice - The slice to submit.\n * @returns {Promise<void>}\n */\nSupervisor.prototype.recordResult = function Supervisor$recordResult (slice)\n{\n // It is possible for slice.result to be undefined when there are upstream errors.\n if ( !(slice && slice.result))\n throw new Error(`recordResult: slice.result is undefined for slice ${slice.identifier}. This is ok when there are upstream errors.`); \n if (!slice.isComplete)\n throw new Error('Cannot record result for slice that has not completed execution successfully.');\n\n debugging('supervisor') && console.debug(`supervisor: recording result for slice ${slice.identifier}.`);\n\n /* @see result-submitter::result for full message details */\n const metrics = { GPUTime: 0, CPUTime: 0, CPUDensity: 0, GPUDensity: 0, total: 0 };\n const payloadData = {\n slice: slice.sliceNumber,\n job: slice.jobAddress,\n worker: this.workerId,\n paymentAddress: this.paymentAddress,\n metrics,\n authorizationMessage: slice.authorizationMessage,\n }\n\n const timeReport = slice.timeReport;\n if (timeReport)\n {\n debugging('supervisor') && console.debug('recordResult timeReport', timeReport);\n // If slice takes less than 1ms to execute, CPUTime will be 0, so compensate.\n if (timeReport.CPU < 1)\n {\n timeReport.CPU++;\n timeReport.total++;\n }\n if (timeReport.total < timeReport.CPU + timeReport.webGL)\n {\n // Compensate or throw? For now we compensate.\n debuggingWarn && console.warn(`Supervisor.recordResult:: Inconsistent time report -- total < CPU + webGL -- ${stringify(timeReport)}`)\n //throw new Error(`recordResult: Inconsistent time report -- total < CPU + webGL -- ${stringify(timeReport)}`)\n timeReport.total = timeReport.CPU + timeReport.webGL;\n }\n if (timeReport.total > 0)\n {\n slice.jobManager.updateStatistics(timeReport);\n metrics.total = timeReport.total;\n metrics.CPUTime = timeReport.CPU;\n metrics.GPUTime = timeReport.webGL;\n metrics.CPUDensity = metrics.CPUTime / timeReport.total;\n metrics.GPUDensity = metrics.GPUTime / timeReport.total;\n }\n }\n\n this.emit('submittingResult');\n\n if (!this.resultSubmitter)\n this.connectTo('resultSubmitter');\n\n if (slice.resultStorageType === 'pattern')\n return this.sendResultToRemote(slice)\n .then((response) => {\n payloadData.result = response;\n this.sendToResultSubmitter(slice, payloadData);\n });\n\n payloadData.result = encodeDataURI(slice.result.result);\n return this.sendToResultSubmitter(slice, payloadData);\n}\n\n/**\n * @param {Slice} slice\n * @param {*} payloadData\n * @returns {Promise<void>}\n */\nSupervisor.prototype.sendToResultSubmitter = function Supervisor$sendToResultSubmitter (slice, payloadData)\n{\n const that = this;\n function handleRSError (error, payloadData)\n {\n that.error(`Failed to submit results to scheduler for slice ${payloadData.slice} of job ${payloadData.job}`, error);\n //slice.jobManager.dumpSlices('recordResult');\n that.saveForResubmitToRS('result', payloadData)\n .then((msg) => {\n if (!error && msg) error = new Error(`resultSubmitter is ${msg}`);\n that.emit('submitSliceFailed', error);\n throw error;\n });\n }\n\n try\n {\n debugging('supervisor') && console.debug('Supervisor.recordResult: payloadData', payloadData.result.slice(0, 256), slice.identifier);\n\n return this.resultSubmitter.send('result', payloadData)\n .then((resp) => {\n if (!resp.success)\n throw resp.payload;\n\n debugging('supervisor') && console.debug('recordResult: SUCCESS', slice.identifier);\n\n const receipt = {\n accepted: true,\n payment: resp.payload.slicePaymentAmount,\n };\n this.emit('submittedResult', resp.payload);\n this.emit('dccCredit', receipt);\n })\n .catch ((error) => {\n handleRSError (error, payloadData);\n });\n }\n catch (error)\n {\n handleRSError (error, payloadData);\n }\n finally\n {\n slice.markAsFinished();\n this.emit('submitFinished');\n // Remove the slice from the job manager.\n slice.jobManager.removeSlice(slice);\n if (this.sliceTiming)\n {\n slice['resultDelta'] = Date.now() - slice['resultDelta'];\n console.debug(`recordResult(${slice['queueingDelta']}, ${slice['executionDelta']}, ${slice['resultDelta']}): Completed slice ${slice.identifier}.`);\n }\n if (selectiveDebugging)\n {\n if (!this.resultMap[slice.jobAddress]) this.resultMap[slice.jobAddress] = { slices: [], totalTimes: [] };\n this.resultMap[slice.jobAddress].slices.push(slice.sliceNumber);\n this.resultMap[slice.jobAddress].totalTimes.push(payloadData.metrics.total);\n }\n }\n}\n\n/**\n * Send a work function's result to a server that speaks our DCP Remote Data Server protocol.\n * E.g. https://gitlab.com/Distributed-Compute-Protocol/dcp-rds\n *\n * @param {Slice} slice - Slice object whose result we are sending.\n * @returns {Promise<string>}\n * @throws When HTTP status not in the 2xx range.\n */\nSupervisor.prototype.sendResultToRemote = function Supervisor$sendResultToRemote (slice)\n{ \n /** XXXpfr @todo: Support file upload and other contentTypes. */\n function serializeContent(result, postParams) {\n const ctArray = postParams.contentType.split(';');\n switch (ctArray[0]) {\n case 'application/json':\n return JSON.stringify(result);\n case 'application/kvin':\n return (__webpack_require__(/*! kvin */ \"./node_modules/kvin/kvin.js\").serialize)(result);\n case 'application/octet-stream':\n case 'application/gzip':\n if (ctArray.length === 2 && ctArray[1] === 'base64')\n return btoa(result);\n // fall-through\n case 'text/plain':\n case 'text/html':\n case 'text/css':\n case 'text/csv':\n case 'text/javascript':\n return result;\n\n default:\n {\n const mtArray = ctArray[0].split('/');\n if (mtArray[0] === 'image' || mtArray[0] === 'video' || mtArray[0] === 'audio')\n return (ctArray.length === 2 && ctArray[1] === 'base64') ? btoa(result) : result;\n throw new Error(`sendResultToRemote: Unsupported contentType ${postParams.contentType}`);\n }\n }\n }\n\n // Construct postParams.\n const postParams = { ...slice.resultStorageParams };\n if (!postParams.contentType)\n postParams.contentType = 'application/json';\n if (!postParams.element)\n postParams.element = slice.sliceNumber;\n debugging('supervisor') && console.debug('sendResultToRemote postParams: ', postParams);\n\n // Construct result.\n const result = slice.result.result;\n if (result)\n postParams.content = serializeContent(result, postParams);\n else\n postParams.error = serializeContent(slice.error, postParams);\n debugging('supervisor') && console.debug('sendResultToRemote content: ', (result ? postParams.content : postParams.error).slice(0, 512));\n\n // Construct url.\n const sliceResultUri = makeValueURI('pattern', slice.resultStorageDetails, {\n slice: slice.sliceNumber,\n job: slice.jobAddress,\n });\n debugging() && console.debug('sendResultToRemote sliceResultUri: ', sliceResultUri);\n const url = new DcpURL(sliceResultUri);\n\n // Check allowed origins.\n if (this.makeSafeOriginList('sendResults').indexOf(url.origin) === -1)\n throw new Error(`Invalid origin for remote result storage: '${url.origin}'`);\n\n // Fetch.\n return justFetch(url, 'JSON', 'POST', false, postParams)\n .then((response) => encodeDataURI(JSON.stringify(response)));\n}\n\n// _Idx\n//\n// Reject.\n//\n\n/**\n * Handles reassigning or returning a slice that was rejected by a sandbox.\n *\n * If the slice does not have a rejected property already, reassign the\n * slice to a new sandbox and add a rejected property to the slice to\n * indicate it has already rejected once.\n *\n * If the slice rejects with a reason, or has a rejected time stamp\n * (ie. has been rejected once already) then return all slices from the\n * job to the scheduler and terminate all sandboxes with that jobAddress.\n *\n * The sandbox will be terminated.\n *\n * @param {Sandbox} sandbox\n * @param {Slice} slice\n */\nSupervisor.prototype.handleWorkReject = async function Supervisor$handleWorkReject (sandbox, slice, rejectReason)\n{\n debugging() && console.debug('handleWorkReject', rejectReason, slice.rejectedTimeStamp, slice.identifier);\n\n // Do a hard flush of the microtask queue and finish the current event loop.\n await new Promise((resolve) => setImmediate(() => setTimeout(resolve, 0)));\n\n const jobManager = slice.jobManager;\n jobManager.rejectedJobReasons.push(rejectReason); // memoize reasons\n\n // First time rejecting without a reason. Try assigning slice to a new sandbox.\n if (rejectReason === 'false' && !slice.rejectedTimeStamp)\n {\n // Set rejected time stamp.\n slice.rejectedTimeStamp = Date.now();\n // Schedule the slice for execution.\n jobManager.scheduleSlice(slice, true /* placeInTheFrontOfTheQueue*/);\n // Slice has been rescheduled, but we still need to terminate the sandbox.\n jobManager.returnSandbox(sandbox);\n }\n else\n { \n // Slice has a reason OR rejected without a reason already and got stamped.\n // Add to array of rejected jobs.\n let rejectedJob = {\n address: slice.jobAddress,\n reasons: jobManager.rejectedJobReasons,\n }\n this.rejectedJobs.push(rejectedJob);\n\n // Purge the job.\n this.purgeJob(jobManager);\n\n // Tell everyone all about it, when allowed.\n if (jobManager.displayMaxDiagInfo)\n {\n const suffixMsg = '\\n\\tAll slices with the same jobAddress returned to the scheduler.\\n\\tAll sandboxes with the same jobAddress are terminated.';\n if (slice.rejectedTimeStamp)\n this.emit('warning', `work.reject: The slice ${slice.identifier} was rejected twice.${suffixMsg}`);\n else\n this.emit('warning', `work.reject: The slice ${slice.identifier} was rejected with reason ${rejectReason}.${suffixMsg}`);\n }\n }\n}\n\n// _Idx\n//\n// Unused functions that we need to review.\n// 1) destroy, shutdown, halt -- possibly need to incorporate these ideas in stopWork\n// 2) predictLoad -- XXXpfr: I really feel bad about not being able to figure out how to incorporate\n// this into the design of sup2. This was a central part of Wes' design of sup2.\n// I need to collaborate with Wes to resolve my ignorance.\n//\n\n/**\n * UNUSED\n * @deprecated -- may use later\n **/\nSupervisor.prototype.destroy = function Supervisor$destory()\n{\n selectiveDebugging && console.debug(`Supervisor.screenSaverDestroy: destroying Supervisor and everything else.`);\n this.stopWork(true /*forceTerminate*/);\n if (this.state) this.state.destroy();\n if (this.progressReportTimer) clearInterval(this.progressReportTimer);\n if (this.watchdogTimer) clearInterval(this.watchdogTimer);\n this.state = null;\n this.progressReportTimer = null;\n this.watchdogTimer = null;\n this.jobManagerInventory = null;\n this.sandboxInventory = [];\n this.readiedSandboxes = [];\n this.closeConnections();\n}\n\n/**\n * UNUSED\n * @deprecated -- may use later \n * Halt the Supervisor as quickly as possible.\n **/\nSupervisor.prototype.halt = function Supervisor$halt()\n{\n this.state.setIf('ready', 'stopping');\n if (!this.state.is('stopping'))\n throw new Error(`Supervisor has an invalid state ${this.state} for halt`);\n clearInterval(this.watchdogTimer);\n\n for (let jobMan of this.jobManagerInventory)\n {\n jobMan.state.setIf('ready', 'stop');\n for (const sandbox of jobMan.workingSandboxes)\n {\n sandbox.stop(); // NYI -- will terminate.\n }\n }\n}\n \n/**\n * UNUSED\n * @deprecated -- may use later \n * Shutdown the supervisor; attempts to return work which will not be finished before timeout expires.\n * The shutdown is complete once this supervisor emits the stopped state change.\n */\nSupervisor.prototype.shutdown = function Supervisor$shutdown(timeoutMs)\n{\n var ps = [], returnSliceInventory = [];\n var timer;\n\n this.state.setIf('ready', 'stopping');\n if (!this.state.is('stopping'))\n throw new Error(`Supervisor has an invalid state ${this.state} for shutdown`);\n clearInterval(this.watchdogTimer);\n\n for (let jobMan of this.jobManagerInventory)\n {\n jobMan.state.setIf('ready', 'stop');\n\n for (let slice of jobMan.sliceInventory)\n {\n if (slice.state.is('initial') || slice.state.is('ready'))\n {\n returnSliceInventory.push(slice);\n }\n else if (slice.state.is(sliceStatus.working))\n {\n ps.push(new Promise((resolve, reject) => {\n slice.state.on('change', (status) => {\n if (status === 'done')\n resolve();\n });\n }));\n }\n }\n }\n\n const reason = 'Supervisor.shutdown';\n this.returnSlices(returnSliceInventory, reason);\n timer = setTimeout(this.halt.bind(this), timeoutMs);\n Promise.all(ps)\n .then(() => {\n clearTimeout(timer);\n this.state.set('stopping', 'stopped');\n })\n .catch((e) => {\n if (e.code !== 'DCP_SUPERVISOR_ESYNC')\n throw e; /* becomes unhandled rejection */\n });\n}\n\n/** \n * Factory function which generates a list of origins which are safe to communicate \n * with for this purpose. Currently-valid purposes (more will be added):\n * - any\n * - fetchData\n * - fetchWorkFunctions\n * - fetchArguments\n * - sendResults\n */\nSupervisor.prototype.makeSafeOriginList = function Supervisor$$makeSafeOriginList(purpose)\n{\n var list = [];\n \n if (this[purpose])\n list = list.concat(this[purpose]);\n \n /* Add 'any' origin(s) to list iff not in localExec, or in localExec and purpose is sendResults */\n if (!this.options.localExec || (this.options.localExec && purpose === 'sendResults'))\n list = list.concat(this.allowedOrigins)\n \n return list;\n}\n \n/**\n * UNUSED -- DOES NOT WORK YET.\n * NEED TO WORK WITH WES TO FIGURE OUT BEST WAY TO GET PREDICTLOAD TO WORK.\n * Predict the load on this supervisor based on the local job measurement data.\n * Works by looking at current conditions and available slices, and tries to guess\n * in what order they will be finished, working, etc. \n *\n * The simulation is very naive, but is expected to be accurate several seconds\n * into the future, particularly as we approach the end of a task.\n *\n * @param {number} whenMs \n * \n * @returns {Object<load, jobManagerInventory>} where load is and instance of Load and the predicted \n * load at the prediction time, and jobManagerInventory \n * is a counterfeit which holds the predicted state of \n * the jobManagerInventory at that time.\n */\nSupervisor.prototype.predictLoad = function Supervisor$predictLoad (whenMs)\n{\n /** @type {JobManager[]} */\n var jmi = new Inventory(); /* Inventory of counterfeit JobManagers. */\n var load = new Load(0,0); /* This \"current\" load throughout the prediction. */\n /** @type {Slice} */\n var next; /* The next slice to \"finish\". */\n\n /* Initialize data structures for prediction from current activity. */\n for (let jobMan of this.jobManagerInventory.filter(jm => jm.state.is('ready') && jm.sliceInventory.length))\n {\n jobMan = jobMan.counterfeit();\n jmi.push(jobMan);\n jobMan.sliceInventory.forEach((s) => s.state.setIf('initial', 'ready'));\n }\n next = findNextSlice();\n \n /**\n * Routine that finds the slice that will end next (soonest.)\n * @returns {Slice}\n */\n function findNextSlice()\n {\n /** @type {Slice} */\n var _next;\n for (let jobMan of jmi)\n {\n const _workingSlices = jobMan.workingSlices;\n for (let slice of _workingSlices)\n {\n //\n // slice.etaMs is the estimated time interval until slice execution completes.\n //\n // If the slice hasn't started,\n // slice.etaMs = slice.jobManager.estimateWallMs,\n // else if the slice has completed execution:\n // slice.etaMs = 0.\n // else if the slice has started:\n // slice.jobManager.estimateWallMs - (Date.now() - slice.startTime).\n //\n if (_next && (_next.etaMs <= slice.etaMs))\n continue;\n\n _next = slice;\n }\n }\n load.add(_next.jobManager.metrics);\n \n return _next;\n }\n\n /* At this point, jmi is an Inventory of counterfeit job managers that are \"ready\" for\n * work, next.etaMs is the time interval until the next slice will finish, and we have\n * a reasonably accurate picture of our current load.\n *\n * Next, we \"end\" this slice, try to fill all cores, and push the timeline forward to\n * the next predicted end of slice.\n */\n for (next = findNextSlice();\n next && (next.etaMs < whenMs);\n next = findNextSlice())\n {\n let ended = next;\n let cursor = this.makeJobSelectionCursor(jmi);\n\n /* \"end\" this slice */\n load.subtract(ended.jobManager.metrics);\n /* Fake out collecting result to transition state to FINISHED. */\n ended.collectResult(null);\n\n /* \"start\" as many slices as we can - given our CPU/GPU constraints, slice data in memory, etc */\n while (this.targetLoad.fits(load))\n {\n let slice = cursor.next();\n if (!slice)\n break; /* Running out of work that fits. */\n\n if (!load.fits(this.targetLoad, slice.jobManager.metrics))\n continue;\n\n /* Pick a ready slice from this job and add its anticipated load to our current load if it will fit */\n slice = slice.jobManager.readySlices.shift();\n slice.markAsWorking(); // ?? Not sure this is correct.\n //slice.etaMs = ended.etaMs + slice.jobManager.estimateWallMs; wtf?!?! <--- LOOK HERE\n\n load.add(slice.jobManager.metrics);\n }\n }\n\n return { load, jobManagerInventory: jmi };\n}\n\n\n//# sourceURL=webpack://dcp/./src/dcp-client/worker/supervisor2/index.js?");
|
|
4456
4529
|
|
|
4457
4530
|
/***/ }),
|
|
4458
4531
|
|
|
@@ -4551,6 +4624,17 @@ eval("/* provided dependency */ var Buffer = __webpack_require__(/*! ./node_modu
|
|
|
4551
4624
|
|
|
4552
4625
|
/***/ }),
|
|
4553
4626
|
|
|
4627
|
+
/***/ "./src/node-libs/shutdown.js":
|
|
4628
|
+
/*!***********************************!*\
|
|
4629
|
+
!*** ./src/node-libs/shutdown.js ***!
|
|
4630
|
+
\***********************************/
|
|
4631
|
+
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
|
4632
|
+
|
|
4633
|
+
"use strict";
|
|
4634
|
+
eval("/**\n * @file shutdown.js \n *\n * Generic code to handle the trapping of uncaught promise\n * rejections and POSIX signals in network daemons, with\n * error reporting propagation to the clients.\n *\n * There are three kinds of shutdown operations:\n * 1 - graceful tasks: these are always invoked and tell the graceful\n * shutdown handler whether or not they were successful. Async\n * functions are supported and functions returning promises are\n * awaited. Unsuccessful shutdown attempts return false.\n *\n * 2 - forced tasks: these are invoked if the graceful tasks do not\n * complete after a specified time. The process will exit in the\n * same pass of the event loop as the execution of the forced tasks.\n *\n * 3 - shutdown functions: these are always invoked and their return\n * values are ignored; similar to POSIX atexit()\n *\n * Shutdown functions and forced shutdowns are hooked from process.exit,\n * and as such always get invoked on exit if registered. In order to\n * perform a graceful shutdown, the exports.manage() should be used to\n * wrap the program's main function OR the exports.shutdown() will need\n * to be explicitly invoked.\n *\n * IMPORTANT: Do not call process.exit() from a shutdown function or task.\n * Use process.exitCode to set a non-zero exit code. This \n * library will not override a non-zero exit code from the \n * application program.\n *\n * @author Wes Garland, wes@kingsds.network\n * @date July 2018, May 2021\n */\n\n\nconst process = __webpack_require__(/*! process */ \"./node_modules/process/browser.js\");\nconst { leafMerge } = __webpack_require__(/*! dcp/utils */ \"./src/utils/index.js\");\nconst { assert } = __webpack_require__(/*! dcp/common/dcp-assert */ \"./src/common/dcp-assert.js\");\nconst { setBackoffInterval, clearBackoffInterval } = __webpack_require__(/*! dcp/common/dcp-timers */ \"./src/common/dcp-timers.js\");\nconst { requireNative } = __webpack_require__(/*! dcp/dcp-client/webpack-native-bridge */ \"./src/dcp-client/webpack-native-bridge.js\");\n\nconst taskRegistry = [];\nconst registeredAtFunctions = [];\nconst debugging = (__webpack_require__(/*! dcp/debugging */ \"./src/debugging.js\").scope)('shutdown');\n\nvar timeDilation = 1;\ntry\n{\n if (requireNative('module')._cache.niim instanceof requireNative('module').Module)\n {\n timeDilation = 10;\n debugging('timeDilation') && console.debug(`053: Running inside niim; timeDilation is ${timeDilation}`);\n }\n}\ncatch (error)\n{\n debugging('timeDilation') && console.debug(`058: Failed to requireNative('module'); probably not running in Node`);\n}\n\nconst defaultOptions = {\n gracefulTimeout: \"15.0\",\n backoffFactor: 1.1,\n baseInterval: 1000,\n maxInterval: 3000\n};\n\n/**\n * Analogue of POSIX atexit(2). Invokes a function when the process is exiting.\n * Ideally, this is what process.on('exit') would do, but it doesn't fire in\n * the case of default signal handlers.\n */\nexports.atExit = function shutdown$$atExit(fn)\n{\n (__webpack_require__(/*! ./signal-handler */ \"./src/node-libs/signal-handler.js\").init)();\n process.on('dcpExit', fn);\n}\n\nexports.atExitCancel = function shutdown$$atExit(fn)\n{\n process.off('dcpExit', fn);\n}\n\n/** Register a task which is to be executed during the shutdown of a server.\n *\n * @param {Function} taskFn The function to execute. When invoked, this function receives\n * a single argument, which is the reason for the shutdown (the\n * why object from inside the server manager). The taskFn function\n * must return true to indicate that its job has been completed;\n * otherwise it will be tried again (and again) during shutdown.\n * This function /must/ behave in a 'synchronous' (blocking) manner.\n * The taskFn function will not be invoked until after we have tried\n * to close all of the client sockets.\n *\n * @param {boolean} [gracefulOnly=false] Execute only when performing graceful shutdown\n * @param {boolean} [forcedOnly=false] Execute only when performing forced shutdown\n */\nexports.registerTask = function shutdown$$registerTask(taskFn, gracefulOnly, forcedOnly)\n{\n var registration = {\n fn: taskFn,\n graceful: !forcedOnly,\n forced: !gracefulOnly\n };\n\n debugging() && console.debug(`shutdown: registered task ${taskFn.name} ${gracefulOnly ? '- graceful only' : ''} ${forcedOnly ? '- forced only' : ''}`);\n assert(!(forcedOnly && gracefulOnly));\n \n taskRegistry.push(registration);\n}\n\n/** Unregister all previously-registered shutdown tasks matching taskFn.\n *\n * @see exports.registerTask\n *\n * @param taskFn The function which was previously registered\n * @param gracefulOnly Only unregister the graceful-shutdown invocation of this function\n * @param forcedOnly Only unregister the forced-shutdown invocation of this function\n *\n * @returns true if the task was located in the task registry\n */\nexports.unregisterTask = function shutdown$$unregisterTask(taskFn, gracefulOnly, forcedOnly)\n{\n var ret = false;\n\n debugging() && console.debug(`shutdown: unregistering task ${taskFn.name} ${gracefulOnly ? '- graceful only' : ''} ${forcedOnly ? '- forced only' : ''}`);\n\n for (let i=0; i < taskRegistry.length; i++)\n {\n let registration = taskRegistry[i];\n\n if (registration.fn !== taskFn)\n continue;\n\n if (gracefulOnly)\n registration.graceful = false;\n if (forcedOnly)\n registration.forced = false;\n \n if ((!registration.forced && !registration.graceful) || (!gracefulOnly && !forcedOnly))\n taskRegistry.splice(i--, 1);\n\n debugging() && console.debug(`shutdown: unregistered ${taskFn.name}`);\n\n ret = true;\n }\n\n return ret;\n}\n\n/**\n * Shutdown reasons can be varied and are intended to be convenient to use for the caller\n * from whatever context/information happens to be at hand.\n *\n * Once this function returns, the new reason should be something we can handle elegantly\n * from inside this code or inside a shutdown task. The following properties will always\n * be defined:\n * why: root cause of shutdown decision (eg SIGINT); should be short and unique so that\n * shutdown tasks can inspect it to make choices\n * mesage: root cause of shutdown decision in human-readable text.\n */\nfunction fixReason(reason)\n{\n if (typeof reason === 'string')\n reason = { why: reason, message: reason };\n if (!reason.why && reason instanceof Error)\n reason.why = reason.name;\n if (!reason.why && !reason.message)\n {\n reason.why = 'unknown';\n reason.message = new Error().stack.split('\\n').slice(2).join('\\n');\n }\n if (!reason.why)\n reason.why = reason.message;\n if (!reason.message)\n reason.message = reason.why;\n\n return reason;\n}\n\n/**\n * Invoke each graceful shutdown handler once, await their results, unregistering if they succeed. A \"failing\" \n * task causes this function to bail without processing any further tasks. This allows us, for example, to \n * implement a daemon that can stay alive during heavy traffic and then bounce when there is a break in user\n * activity.\n *\n * @param {Object} reason Reason object to pass to shutdown task\n * @returns true if no handlers returned false.\n */\nasync function invokeGracefulShutdownTasks(reason)\n{\n reason = fixReason(reason);\n debugging() && console.debug('Graceful shutdown -', reason.message);\n \n while(taskRegistry.length)\n {\n let registration = taskRegistry[taskRegistry.length - 1];\n let taskFn = registration.fn;\n\n if (!registration.graceful)\n continue;\n \n try\n {\n let notOk = await taskFn(reason) === false;\n debugging() && console.debug(`shutdown: graceful task ${taskFn.name} ${notOk ? 'did not run' : 'ran'} ok`);\n if (notOk)\n return false;\n }\n catch(e)\n {\n if (!(e instanceof Error))\n console.warn(`Warning: Shutdown task ${taskFn.name} rejected with type=${typeof e}, which is not an instanceof Error`);\n debugging() && console.debug(`shutdown: graceful task ${taskFn.name} failed (will retry); ${e && e.message}`);\n return false;\n }\n \n exports.unregisterTask(taskFn);\n debugging() && console.log('debugging: tasks left:', taskRegistry.length);\n }\n\n return true;\n}\n\n/* Invoke each forced shutdown handler once.\n *\n * @param {Object} reason Reason object to pass to shutdown task\n */\nfunction invokeForcedShutdownTasks(reason)\n{\n reason = fixReason(reason);\n debugging() && console.debug('shutdown: starting forced shutdown -', reason);\n\n for (let registration of taskRegistry)\n {\n let taskFn = registration.fn;\n \n if (registration.forced)\n {\n try\n {\n taskFn(reason);\n debugging() && console.debug(`shutdown: ran forced task ${taskFn.name}`);\n }\n catch(e)\n {\n console.warn(`shutdown: forced shutdown task ${taskFn.name} failed`, e);\n }\n }\n\n exports.unregisterTask(taskFn);\n debugging() && console.log('debugging: tasks left:', taskRegistry.length);\n }\n}\n\n/** \n * Register a shutdown function.\n *\n * These functions are invoked once during the shutdown process. They are different from shutdown \n * tasks in that they are fire-and-forget rather than monitored and potentially retried. Functions are\n * run in LIFO order, and before any of the shutdown tasks.\n *\n * This function hooks process.exit, so the shutdown functions and the forced shutdown tasks are\n * run, if registered, regardless of whether or not this is a managed process.\n *\n * @param {function} fn a function to run at shutdown\n */ \nexports.atShutdown = function shutdown$$atShutdown(fn)\n{\n registeredAtFunctions.push(fn);\n debugging() && console.debug('shutdown: registered function', fn.name || '(anonymous)');\n\n if (exports.atShutdown.ran !== true)\n {\n exports.atShutdown.ran = true;\n process.on('dcpExit', function atShutdownHandler() {\n debugging() && console.debug('shutdown: atShutdownHandler');\n invokeForcedShutdownTasks('process exit');\n invokeShutdownFunctions('process exit');\n });\n }\n}\n\nfunction invokeShutdownFunctions(reason)\n{\n var fn;\n \n reason = fixReason(reason);\n debugging() && console.debug(`shutdown: running ${registeredAtFunctions.length} shutdown functions (${reason.message}, exitCode=${process.exitCode})`);\n\n while ((fn = registeredAtFunctions.pop()))\n {\n try\n {\n fn();\n debugging() && console.debug('shutdown: ran shutdown function', fn.name);\n }\n catch (error)\n {\n console.error(`Error running shutdown function ${fn.name}:`, error);\n }\n debugging() && console.log('debugging: functions left:', registeredAtFunctions.length);\n }\n}\n\n/**\n * Perform the clean shutdown proceedure.\n *\n * This function will eventually invoke process.exit() with one of the following exit codes, \n * provided no non-zero value was specified for process.exitCode before this function was called:\n *\n * - 0 => clean exit\n * - 50 => forced shutdown after graceful attempt failed\n * - 51 => error during shutdown\n * - 52 => shutdown because managed function threw an exception\n * - 53 => shutdown because managed function had unhandled rejection\n * - 60 => impossible code block reached\n * - 61...82 => reserved for signal handlers; suggest: 60 + signal #\n *\n * WARNING - this function should be awaited or the very last thing your daemon calls. Otherwise,\n * your daemon can wind up trying to \"do work\" during shutdown, which might not go well.\n * \n * A \"keepalive\" timer is started at the beginning of this function with the express purpose of\n * preventing Node.js from exiting due to an empty/unref event loop once this process has begun.\n *\n * @param {string|object} reason the reason we are shutting down\n * @param {object} options overrides for dcpConfig.daemons.shutdown\n * @param {boolean} force if true, go straight to forced shutdown\n */\nexports.shutdown = async function shutdown (reason, force, options = {})\n{\n var nodeKeepalive;\n var backoffHnd;\n\n reason = fixReason(reason);\n options = leafMerge(defaultOptions, (typeof dcpConfig !== 'undefined' ? dcpConfig.daemons.shutdown : {}), options);\n debugging() && console.debug(`shutdown: start; ${taskRegistry.length} tasks; ${registeredAtFunctions.length} at-functions;`, reason.why, options);\n \n if (timeDilation !== 1)\n {\n /* slow things down in the debugger */\n options.gracefulTimeout *= timeDilation;\n options.baseInterval *= timeDilation;\n options.maxInterval *= timeDilation;\n }\n \n function forceShutdown()\n {\n debugging() && console.debug('shutdown: beginning forced shutdown phase');\n \n try\n {\n clearBackoffInterval(backoffHnd);\n }\n catch(e){};\n \n try\n {\n invokeForcedShutdownTasks(reason);\n invokeShutdownFunctions(reason);\n }\n catch(e)\n {\n console.error(e);\n }\n\n process.exit();\n }\n\n if (exports.shutdown.timer)\n return false; /* ignore multi-trigger */\n\n try\n {\n if (!options.quiet)\n console.log(`Shutting down, ${reason.message}` + (force ? ' (forced)' : ''));\n if (reason instanceof Error && !options.quiet)\n console.error('Shutdown reason stack:', reason.stack);\n\n reason = reason || new Error().stack.split('\\n')[1];\n if (typeof reason === 'string')\n reason = { message: reason };\n\n /**\n * Set up a forced shutdown timer that fires if the graceful shutdown fails\n * or if a shutdown function never resolves. Then create a dummy timer which\n * keeps node from exiting due to an empty event loop, just in case, so that\n * we can ensure we have control of the shutdown process.\n */\n debugging() && console.debug('shutdown: graceful timeout is', options.gracefulTimeout + 's');\n function gracefulTimeoutElapsed() {\n process.exitCode = 50;\n forceShutdown();\n }\n exports.shutdown.timer = setTimeout(gracefulTimeoutElapsed, 1000 * options.gracefulTimeout).unref();\n nodeKeepalive = setTimeout(Function, 1 << 31 /* \"infinity\" */);\n\n if (force === true)\n {\n forceShutdown();\n process.exit(60);\n }\n\n /* Try running the graceful shutdown tasks on an exponential backoff until no\n * tasks fail; when this happens, clear the forced shutdown timer and exit.\n * Note that this promise is purposefully never resolved. Process.exit is \n * invoked via process.nextTick so that anything else on the event loop can\n * unwind but with no danger that new events will be handled.\n */\n await new Promise(function gracefulShutdown(resolve) {\n var backoffHnd;\n\n async function gracefulTry()\n {\n if (gracefulTry.trying)\n return;\n\n gracefulTry.trying = true;\n try\n {\n let res = await invokeGracefulShutdownTasks(reason);\n gracefulTry.trying = false;\n if (res === false)\n return; // not dead yet\n debugging() && console.log('shutdown: finished graceful shutdown tasks');\n \n clearTimeout(nodeKeepalive);\n clearBackoffInterval(backoffHnd);\n clearTimeout(exports.shutdown.timer);\n invokeShutdownFunctions('post-graceful');\n resolve(); /* drop to process.exit in finally block */\n }\n finally\n {\n gracefulTry.trying = false;\n }\n }\n \n backoffHnd = setBackoffInterval(gracefulTry, options);\n gracefulTry();\n });\n }\n catch(error)\n {\n console.log(error);\n if (!process.exitCode)\n process.exitCode = 51;\n }\n finally\n {\n process.exit();\n }\n}\n\n/**\n * Manage a process with the default shutdown hooks.\n *\n * @return an Object with functions corresponding to the different shutdown handlers, \n * sigInt, sigQuit, sigTerm, uncaughtException and unhandledRejection. During \n * shutdown trigger, this object is used as a jump table, so the object can be \n * used to either disable triggers or replace them as needed.\n */\nexports.manageProcess = function shutdown$$manageProcess()\n{\n const shutdown = exports.shutdown;\n var jumpTable;\n\n function unhandledRejection(error, promise)\n {\n let options = leafMerge(defaultOptions, (typeof dcpConfig !== 'undefined' ? dcpConfig.daemons.shutdown : {}));\n \n if (!(error instanceof Error))\n error = new Error('Unhandled Rejection:' + error);\n process.exitCode = process.exitCode || 53;\n console.error('\\nunhandled rejection:', error, 'at:', promise);\n if (options.liveDangerously)\n {\n console.warn('living dangerously - not killing program');\n return;\n }\n error.why = 'unhandledRejection';\n shutdown(error, true);\n }\n\n function uncaughtException(error, origin)\n {\n let options = leafMerge(defaultOptions, (typeof dcpConfig !== 'undefined' ? dcpConfig.daemons.shutdown : {}));\n\n process.exitCode = process.exitCode || 53;\n console.error('\\nuncaught exception:', error);\n error.why = 'uncaughtException';\n if (options.liveDangerously)\n {\n console.warn('living dangerously - not killing program');\n return;\n }\n shutdown(error, true);\n }\n\n function sigInt()\n {\n process.exitCode = process.exitCode || 63;\n shutdown({why: 'SIGINT', message: 'Trapped signal 2'});\n jumpTable.sigInt = doubleTap;\n }\n\n function sigQuit()\n {\n process.exitCode = process.exitCode || 70;\n shutdown({why: 'SIGQUIT', message: 'Trapped signal 10'});\n jumpTable.sigQuit = doubleTap;\n }\n\n function sigTerm()\n {\n process.exitCode = process.exitCode || 75;\n shutdown({why: 'SIGTERM', message: 'Trapped signal 15'});\n jumpTable.sigTerm = doubleTap;\n }\n\n function doubleTap()\n {\n exports.shutdown.timer._onTimeout(); /* expire the graceful shutdown right away */\n }\n \n delete exports.manageProcess; /* don't call twice! */\n \n process.on('unhandledRejection', (...a) => jumpTable.unhandledRejection(...a));\n process.on('uncaughtException', (...a) => jumpTable.uncaughtException (...a));\n process.on('SIGINT', (...a) => jumpTable.sigInt (...a));\n process.on('SIGQUIT', (...a) => jumpTable.sigQuit (...a));\n process.on('SIGTERM', (...a) => jumpTable.sigTerm (...a));\n\n jumpTable = { unhandledRejection, uncaughtException, sigInt, sigQuit, sigTerm };\n return jumpTable;\n}\n\n/** \n * Begin managing a program's shutdown.\n *\n * @param {function} func the program's main function - shutdown begins automatically when it returns or\n * the promise it returns resolves or rejects.\n * @param {array} argv arguments to pass to func\n *\n * @returns the value returned by func.apply(null, argv);, unless it is a Promise, in which case we return a\n * Promise which awaits that value.\n */\nexports.manage = async function shutdown$$manage(func, argv, options)\n{\n const shutdown = exports.shutdown;\n let ret;\n\n exports.manageProcess();\n\n debugging() && console.debug('shutdown: managing function', func.name);\n try\n {\n ret = func.apply(null, argv || []);\n }\n catch(e)\n {\n process.exitCode = process.exitCode || 52;\n console.error(e);\n await shutdown({why: 'uncaughtException', message: e.message}).catch(process.exit);\n process.exit(60);\n }\n\n debugging() && console.debug(`shutdown: ${func.name} was ${ret instanceof Promise ? 'async' : 'sync'}`);\n if (!(ret instanceof Promise))\n {\n await shutdown({why: 'normal exit'})\n process.exit(60);\n }\n \n try\n {\n /* ret is a Promise => func is async */\n await ret\n .then((result) => shutdown({why: 'normal exit', message: 'normal exit (async)'}))\n .catch((error) => shutdown({why: `unhandled rejection ${error.message}`}))\n }\n catch(error)\n {\n console.log(error);\n }\n\n process.exit(60);\n}\n\n\n//# sourceURL=webpack://dcp/./src/node-libs/shutdown.js?");
|
|
4635
|
+
|
|
4636
|
+
/***/ }),
|
|
4637
|
+
|
|
4554
4638
|
/***/ "./src/node-libs/signal-handler.js":
|
|
4555
4639
|
/*!*****************************************!*\
|
|
4556
4640
|
!*** ./src/node-libs/signal-handler.js ***!
|
|
@@ -4558,7 +4642,7 @@ eval("/* provided dependency */ var Buffer = __webpack_require__(/*! ./node_modu
|
|
|
4558
4642
|
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
|
4559
4643
|
|
|
4560
4644
|
"use strict";
|
|
4561
|
-
eval("//#! /usr/bin/env node\n/**\n * @file signal-handler.js Utility to add intermediate POSIX signal handling. Handling is done\n * for signals that terminate the process, which ensures dcpExit is emitted.\n * dcpExit is introduced here as we don't want to call exit ourselves, which would\n * change node behaviour in the user's persepctive. \n *\n *\n * @author Kirill Kirnichansky, kirill@distributive.network\n * @date September, 2022\n */\n\n\nconst DCP_ENV = __webpack_require__(/*! ../common/dcp-env */ \"./src/common/dcp-env.js\");\n\nlet process;\nif (DCP_ENV.platform === 'nodejs')\n{\n const { requireNative } = __webpack_require__(/*! dcp/dcp-client/webpack-native-bridge */ \"./src/dcp-client/webpack-native-bridge.js\");\n process = requireNative('process');\n}\n\nfunction signalHandler(sig)\n{\n // we only want to handle the signal if there are no other user defined handlers\n // this check ensures the only handler for the signal is this one\n if (process.listenerCount(sig) === 1 && process.listeners(sig)[0] === signalHandler)\n {\n process.off(sig, signalHandler);\n process.emit('dcpExit');\n process.kill(process.pid, sig);\n // this is a check for if node changes in the future\n // currently we don't expect an exit event (reason for this handler in the first place)\n process.on('exit', () => {\n const error = new Error('Process exit handler fired unexpectedly!');\n process.emitWarning(error);\n throw error;\n });\n }\n}\n\nexports.init = function init()\n{\n // signals that terminate process\n for (let sig of ['SIGHUP', 'SIGINT', 'SIGQUIT', 'SIGILL', 'SIGTRAP', 'SIGUSR2', 'SIGTERM', 'SIGALRM'
|
|
4645
|
+
eval("//#! /usr/bin/env node\n/**\n * @file signal-handler.js Utility to add intermediate POSIX signal handling. Handling is done\n * for signals that terminate the process, which ensures dcpExit is emitted.\n * dcpExit is introduced here as we don't want to call exit ourselves, which would\n * change node behaviour in the user's persepctive. \n *\n *\n * @author Kirill Kirnichansky, kirill@distributive.network\n * @date September, 2022\n */\n\n\nconst DCP_ENV = __webpack_require__(/*! ../common/dcp-env */ \"./src/common/dcp-env.js\");\nconst debugging = (__webpack_require__(/*! dcp/debugging */ \"./src/debugging.js\").scope)('signals');\n\nlet process;\nif (DCP_ENV.platform === 'nodejs')\n{\n const { requireNative } = __webpack_require__(/*! dcp/dcp-client/webpack-native-bridge */ \"./src/dcp-client/webpack-native-bridge.js\");\n process = requireNative('process');\n}\n\nfunction signalHandler(sig)\n{\n // we only want to handle the signal if there are no other user defined handlers\n // this check ensures the only handler for the signal is this one\n if (process.listenerCount(sig) === 1 && process.listeners(sig)[0] === signalHandler)\n {\n process.off(sig, signalHandler);\n process.emit('dcpExit');\n process.kill(process.pid, sig);\n // this is a check for if node changes in the future\n // currently we don't expect an exit event (reason for this handler in the first place)\n process.on('exit', () => {\n const error = new Error('Process exit handler fired unexpectedly!');\n process.emitWarning(error);\n throw error;\n });\n }\n}\n\nvar __initialized = false;\nexports.init = function init()\n{\n if (__initialized)\n return;\n debugging() && console.debug('initialized signal-handler module');\n // signals that terminate process\n for (let sig of ['SIGHUP', 'SIGINT', 'SIGQUIT', 'SIGILL', 'SIGTRAP', 'SIGUSR2', 'SIGTERM', 'SIGALRM',\n 'SIGABRT', 'SIGFPE', 'SIGSEGV', 'SIGBUS', 'SIGSYS', 'SIGSTKFLT', 'SIGXCPU',\n 'SIGVTALRM', 'SIGPROF', 'SIGIO', 'SIGPWR'])\n {\n debugging('verbose') && console.debug(' - hooked', sig);\n process.on(sig, signalHandler);\n }\n process.on('exit', () => process.emit('dcpExit'));\n}\n\n\n//# sourceURL=webpack://dcp/./src/node-libs/signal-handler.js?");
|
|
4562
4646
|
|
|
4563
4647
|
/***/ }),
|
|
4564
4648
|
|
|
@@ -4611,7 +4695,7 @@ eval("/**\n * @file protocol/connection/message.js\n * @author Ryan
|
|
|
4611
4695
|
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
|
4612
4696
|
|
|
4613
4697
|
"use strict";
|
|
4614
|
-
eval("/* provided dependency */ var process = __webpack_require__(/*! ./node_modules/process/browser.js */ \"./node_modules/process/browser.js\");\n/**\n * @file protocol/connection/connection.js\n * @author Ryan Rossiter\n * @author KC Erb\n * @author Wes Garland\n * @date January 2020, Feb 2021, Mar 2022\n *\n * A Connection object represents a connection to another DCP entity. \n * A DCP connection may 'live' longer than the underlying protocol's connection,\n * and the underlying protocol connection (or, indeed, protocol) may change\n * throughout the life of the DCP connection.\n * \n * DCP connections are uniquely identified by the DCP Session ID, specified by\n * the dcpsid property, present in every message body. This session id negotiated during connection,\n * with the initiator and target each providing half of the string.\n *\n * Connection instance events:\n * - session: dcpsid new session established\n * - connect: url UI hint: internet available\n * - disconnect: url UI hint: internet not available\n * - readyStateChange: *** DO NOT USE **\n * - error: error emitted when errors happen that would otherwise go uncaught\n * - close: connection instance is closing\n * - end: Connection instance is closed\n * - send: msgObj when a message is sent to the peer; does not wait for ack; may re-trigger on reconnect\n * - ready: when the connection is ready for traffic (constructor promises resolved)\n *\n * State Transition Diagram for Connection.state:\n *\n * initial connecting established disconnected close-wait closing closed\n * ===========================================================================================================================\n * |-- i:connect ---->\n * |-- t:accept ----->\n * |-- t:establishTarget -->\n * |-- i:connect ---------->\n * |-- transportDisconnectHandler -->\n * <-- i:_reconnect ----------------------------------------|\n * |-i:useNewTransport-->\n * <-- t:useNewTransport --------|\n * |-- closeWait ----------------------------------------------------------->\n * |-- closeWait ----------------------------------->\n * |-- closeWait -->\n * |-- doClose --------------->\n * |-- close ------------------------------------------------------------------------------------------------------------> \n * |-- close ---------------------------------------------------------------------------->\n * |-- close ---------------------------------------------------->\n * |-- close ------------------->\n * |-- doClose -->\n *\n *\n * Not until the established state can we count on things like a dcpsid, \n * peerAddress, identityPromise resolution and so on.\n * \n * Error Codes relevant to DCP Connections:\n * DCPC-1001 - CONNECTION CANNOT SEND WHEN IN CLOSING, CLOSE-WAIT OR CLOSED\n * EINVAL - MESSAGE OWNER IS INVALID (formerly DCPC-1002)\n * MESSAGE SIGNATURE INVALID (formerly DCPC-1003)\n * MESSAGE BODY IS INVALID (formerly DCPC-1004)\n * DCPC-1005 - TRYING TO ESTABLISH TARGET AFTER TARGET ALREADY ESTABLISHED\n * DCPC-1006 - CONNECTION COULD NOT BE ESTABLISHED WITHIN 30 SECONDS\n * DCPC-1007 - RECEIVED MESSAGE PAYLOAD BEFORE CONNECT OPERATION\n * DCPC-1008 - TARGET RESPONDED WITH INVALID DCPSID\n * DCPC-1009 - MESSAGE IS OF UNKNOWN TYPE\n * DCPC-1010 - DUPLICATE TRANSMISSION RECEIPT\n * DCPC-1011 - DEFAULT ERROR CODE WHEN PEER SENDS CLOSE MESSAGE\n * DCPC-1012 - TRIED TO INITIATE CONNECTION AFTER SESSION ALREADY ESTABLISHED\n * DCPC-1013 - DEFAULT ERROR CODE WHEN CLOSING WITH REASON THATS NOT INSTANCE OF DCPERROR\n * DCPC-1014 - NO TRANSPORTS AVAILABLE\n * DCPC-1015 - CANNOT CONNECT WHEN CONNECTION ALREADY CLOSED\n * DCPC-1016 - ERROR CONNECTING VIA AVAILABLE TRANSPORTS\n * DCPC-1017 - FIRST PROTOCOL MESSAGE WAS DID NOT INVOLVE INITIAL CONNECT REQUEST\n * DCPC-1018 - INVALID ARGUMENT PROVIDED IN PLACE OF IDKEYSTORE\n * ENODCPSID - CONNECTION INSTANCE TRIED TO RE-CONNECT TO A TARGET WHICH DOES NOT HAVE A RECORD OF THAT SESSION\n */\n\n\n\nconst debugging = (__webpack_require__(/*! dcp/debugging */ \"./src/debugging.js\").scope)('dcp');\nconst dcpEnv = __webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\");\nconst { EventEmitter } = __webpack_require__(/*! dcp/common/dcp-events */ \"./src/common/dcp-events/index.js\");\nconst wallet = __webpack_require__(/*! dcp/dcp-client/wallet */ \"./src/dcp-client/wallet/index.js\");\nconst { DcpURL } = __webpack_require__(/*! dcp/common/dcp-url */ \"./src/common/dcp-url.js\");\nconst { requireNative } = __webpack_require__(/*! dcp/dcp-client/webpack-native-bridge */ \"./src/dcp-client/webpack-native-bridge.js\");\nconst { assert } = __webpack_require__(/*! dcp/common/dcp-assert */ \"./src/common/dcp-assert.js\");\nconst { leafMerge, a$sleepMs } = __webpack_require__(/*! dcp/utils */ \"./src/utils/index.js\");\nconst { Synchronizer } = __webpack_require__(/*! dcp/common/concurrency */ \"./src/common/concurrency.js\");\nconst { DCPError } = __webpack_require__(/*! dcp/common/dcp-error */ \"./src/common/dcp-error.js\");\n\nconst { Transport } = __webpack_require__(/*! ../transport */ \"./src/protocol-v4/transport/index.js\");\nconst { Sender } = __webpack_require__(/*! ./sender */ \"./src/protocol-v4/connection/sender.js\");\nconst { Receiver } = __webpack_require__(/*! ./receiver */ \"./src/protocol-v4/connection/receiver.js\");\nconst { MessageLedger } = __webpack_require__(/*! ./message-ledger */ \"./src/protocol-v4/connection/message-ledger.js\");\nconst { getGlobalIdentityCache } = __webpack_require__(/*! ./identity-cache */ \"./src/protocol-v4/connection/identity-cache.js\");\nconst { makeEBOIterator, setImmediateN, setImmediate } = __webpack_require__(/*! dcp/common/dcp-timers */ \"./src/common/dcp-timers.js\");\n\nconst { ConnectionMessage } = __webpack_require__(/*! ./connection-message */ \"./src/protocol-v4/connection/connection-message.js\");\nconst { ConnectionRequest } = __webpack_require__(/*! ./request */ \"./src/protocol-v4/connection/request.js\");\nconst { ConnectionResponse } = __webpack_require__(/*! ./response */ \"./src/protocol-v4/connection/response.js\");\nconst { ConnectionBatch } = __webpack_require__(/*! ./batch */ \"./src/protocol-v4/connection/batch.js\");\nconst { ConnectionAck } = __webpack_require__(/*! ./ack */ \"./src/protocol-v4/connection/ack.js\");\nconst { ErrorPayloadCtorFactory } = __webpack_require__(/*! ./error-payload */ \"./src/protocol-v4/connection/error-payload.js\");\nconst { role } = __webpack_require__(/*! ./connection-constants */ \"./src/protocol-v4/connection/connection-constants.js\");\n\nconst isDebugBuild = (__webpack_require__(/*! dcp/common/dcp-build */ \"./src/common/dcp-build.js\").build) === 'debug';\nlet nanoid;\nif (dcpEnv.platform === 'nodejs') {\n const { requireNative } = __webpack_require__(/*! dcp/dcp-client/webpack-native-bridge */ \"./src/dcp-client/webpack-native-bridge.js\");\n nanoid = requireNative('nanoid').nanoid;\n} else {\n nanoid = (__webpack_require__(/*! nanoid */ \"./node_modules/nanoid/index.browser.js\").nanoid);\n}\n\n\nlet globalConnectionId = 0;\nvar _msgId = 0;\n\nconst CONNECTION_STATES = [\n 'initial',\n 'connecting', /* initiator: establish first transport instance connection; target: listening */\n 'established',\n 'disconnected', /* connection is still valid, but underlying transport is no longer connected */\n 'close-wait', /* Target of close message is in this state until response is acknowledged */\n 'closing',\n 'closed',\n]\n\nclass Connection extends EventEmitter {\n static get VERSION() {\n return '5.1.0'; // Semver format\n }\n\n static get VERSION_COMPATIBILITY() {\n return '^5.0.0'; // Semver format, can be a range\n }\n\n /**\n * Connection form 2:\n * @constructor\n * @param {object} [target]\n * @param {Promise} idKsPromise A promise which resolves to the identity keystore described\n * in form 1\n * @param {object} [options]\n * @see form 1\n */\n /**\n * Connection form 1\n * Create a DCP Connection object. This object could represent either the initiator or \n * target end of a connection, until it is specialized by either invoke the connect()\n * or accept() methods. Note that send() invokes connect() internally if not in an established\n * state.\n * @constructor\n * @note Connection objects exist for the lifetime of a given DCP connection \n * (session), whether or not the underlying transport (eg internet protocol) is connected or not. Once \n * the DCP session has ended, this object has no purpose and is not reusable.\n * \n * @param {object} target Object (usually a dcpConfig fragment) describing the target.\n * This object may contain the following properties; 'location' is\n * mandatory:\n * - location: a URL or DcpURL that is valid from the Internet\n * - friendLocation: a DcpURL that is valid from an intranet; if\n * both location and friendLocation specified, the best one will\n * be chosen by examining IP addresses (assuming an IP bearer).\n * - identity: an object with an address property which is a promise\n * that resolves to an instance of wallet.Address which represents\n * to the target's identity; this overrides the initiator's \n * identity cache unless options.strict is truey.\n * \n * @param {Keystore} [idKeystore] The keystore used to sign messages; used for non-repudiation.\n * If not specified, a dynamically-generated keystore will be used.\n * \n * @param {object} [options] Extra connection options that aren't defined via dcpConfig.dcp.connectionOptions.\n * These options include:\n * - identityUnlockTimeout: Number of (floating-point) seconds to leave the identity \n * keystore unlocked between invocations of Connection.send\n *\n * @returns instance of Connection that is specific to a target URL but not a role\n */\n constructor(target, idKeystore, options = {})\n {\n super('Connection');\n this.id = ++globalConnectionId;\n this.debugLabel = `connection(g:${this.id}):`;\n\n /* polymorphism strategy: rewrite to (configFragment, idksPromise, options) */\n if (target instanceof DcpURL)\n target = { location: target };\n else if (DcpURL.isURL(target))\n target = { location: new DcpURL(target) };\n else if (target instanceof String || typeof target === 'string')\n target = { location: new DcpURL(target) };\n assert(typeof target === 'object', target.location);\n\n /* idKeystore is always resolved by the time a session is established. */\n if (!idKeystore)\n this.identityPromise = wallet.getId();\n else if (idKeystore instanceof Promise)\n this.identityPromise = idKeystore;\n else if (idKeystore instanceof wallet.Keystore)\n this.identityPromise = Promise.resolve(idKeystore); \n else if (idKeystore instanceof wallet.Address)\n this.identityPromise = Promise.resolve(new wallet.Keystore(idKeystore, '')); \n else\n throw new DCPError('Invalid argument provided for IdKeystore', 'DCPC-1018');\n\n this.identityPromise.then((keystore) => {\n /* This always happens by the time a role is assumed */\n delete this.identityPromise;\n this.identity = keystore;\n this.emit('ready');\n debugging('connection') && console.debug(this.debugLabel, 'identity is', this.identity.address);\n });\n\n this.target = target;\n this.url = this.target.location;\n \n // Init internal state / vars\n this.state = new Synchronizer(CONNECTION_STATES[0], CONNECTION_STATES);\n // DO NOT USE this.state.on('change', (s) => this.emit('readyStateChange', s) );\n\n this.dcpsid = null;\n this.peerAddress = null;\n this.transport = null;\n this.messageLedger = new MessageLedger(this);\n this.authorizedSender = null;\n this.preDisconnectState = null;\n \n this.Message = ConnectionMessage(this);\n this.Request = ConnectionRequest(this.Message);\n this.Response = ConnectionResponse(this.Message);\n this.Batch = ConnectionBatch(this.Message);\n this.Ack = ConnectionAck(this.Message);\n this.ErrorPayload = ErrorPayloadCtorFactory(this);\n this.connectTime = Date.now();\n\n this.receiver = new Receiver(this);\n this.sender = new Sender(this);\n \n debugging('connection') && console.debug(this.debugLabel, `new connection#${this.id}; ${this.url}`);\n\n /* Create a connection config as this.options which takes into\n * account system defaults and overrides for specific urls, origins, etc.\n */\n this.options = leafMerge(\n ({ /* hardcoded defaults insulate us from missing web config */\n 'connectTimeout': 90,\n 'lingerTimeout': 1800,\n 'allowBatch': true,\n 'maxMessagesPerBatch': 100,\n 'identityUnlockTimeout': 300,\n 'ttl': {\n 'min': 15,\n 'max': 600,\n 'default': 120\n },\n 'transports': [ 'socketio' ],\n }),\n dcpConfig.dcp.connectionOptions.default,\n this.url && dcpConfig.dcp.connectionOptions[this.url.hostname],\n this.url && dcpConfig.dcp.connectionOptions[this.url.origin],\n options\n );\n\n /* draw out errors quickly in dev */\n if ((process.env.DCP_NETWORK_CONFIG_BUILD || dcpConfig.build) === 'debug')\n {\n this.options.maxMessagesPerBatch /= 10;\n \n /* short timeouts and debuggers don't get along well */\n if (dcpEnv.platform === 'nodejs' && !(requireNative('module')._cache.niim instanceof requireNative('module').Module))\n {\n this.options.connectTimeout /= 10;\n this.options.lingerTimeout /= 20;\n this.options.identityUnlockTimeout /= 10;\n }\n }\n\n assert(this.options.identityUnlockTimeout > 0);\n assert(this.options.connectTimeout > 0);\n assert(this.options.lingerTimeout > 0);\n assert(typeof this.options.ttl.min === 'number');\n assert(typeof this.options.ttl.max === 'number');\n assert(typeof this.options.ttl.default === 'number');\n \n this.backoffTimeIterator = makeEBOIterator(500, dcpConfig.build === 'debug' ? 5000 : 60000); /** XXXwg make this configurable */\n\n this.secureLocation = determineIfSecureLocation(this);\n this.loggableDest = '<generic>';\n }\n \n generateMessageId()\n {\n return `${this.id}-${_msgId++}-${Date.now()}-${nanoid()}`;\n }\n\n /**\n * Specialize an instance of Connection for either initiator or target behaviour. Once specialized,\n * the role cannot be changed. This happens based on methods invoked; connect() or accept() cause\n * the change.\n *\n * This specialization also implies that the connection is fully ready for use, including resolution\n * of the identity promise if necessary. This is perhaps not the best place to ensure that, but it\n * provides a reliable - and already async - waypoint to observe that event.\n */\n async a$assumeRole(myRole)\n {\n assert(myRole === role.initiator || myRole === role.target);\n\n if (this.role === myRole)\n return;\n this.role = myRole;\n \n if (this.role === role.target)\n {\n this.debugLabel = `connection(t:${this.id}):`;\n this.sender.debugLabel = `sender(t#${this.id}):`;\n this.messageLedger.debugLabel = `message-ledger(t#${this.id}):`;\n this.loggableDest = '<target>';\n this.hasNtp = true;\n }\n else\n {\n this.debugLabel = `connection(i:${this.id}):`;\n this.sender.debugLabel = `sender(i#${this.id}):`;\n this.messageLedger.debugLabel = `message-ledger(i#${this.id}):`;\n this.loggableDest = this.url.href;\n this.hasNtp = false;\n }\n\n debugging('connection') && console.debug(this.debugLabel, `connection #${this.id} is ${this.role} for ${this.url}`);\n if (!this.identity)\n {\n assert(this.identityPromise);\n debugging('connection') && console.debug(this.debugLabel, `waiting for identity resolution`);\n await this.identityPromise;\n }\n }\n\n /**\n * API to establish a DCP connection. Implied by send().\n *\n * When invoked by the initiator, this method establishes the connection by connecting\n * to the target url provided to the constructor.\n */\n async connect() // eslint-disable-line require-await\n {\n if (this.role == role.target)\n return;\n \n if (!this.role)\n await this.a$assumeRole(role.initiator);\n \n if (this.state.is('initial'))\n {\n if (!this.connectPromise)\n this.connectPromise = Promise.race([this.connectTimer(), this.a$_connect()]).then(() => { clearTimeout(this.connectTimeoutPromise); delete this.connectPromise });\n return this.connectPromise;\n }\n\n if (this.state.is('disconnected'))\n {\n if (!this.connectPromise)\n this.connectPromise = this.a$_reconnect().then(() => delete this.connectPromise);\n return this.connectPromise;\n }\n \n if (this.state.is('connecting'))\n {\n assert(this.connectPromise);\n return this.connectPromise;\n }\n\n if (this.state.is('established'))\n return;\n \n if (this.state.in(['closed', 'close-wait', 'closing']))\n throw new DCPError('Connection already closed', 'DCPC-1015');\n\n throw new Error('impossible');\n }\n\n /**\n * Performs a reconnection for connections which are in the disconnected state, and\n * tries to send any in-flight or enqueued messages as soon as that happens.\n */\n async a$_reconnect()\n {\n var transport;\n\n this.state.testAndSet('disconnected', 'connecting');\n try\n {\n do\n {\n transport = await this.a$connectToTargetTransport();\n } while (!transport && (this.transport && !this.transport.ready()) && !this.state.in(['closed', 'close-wait', 'closing']));\n\n debugging('connection') && console.debug(this.debugLabel, `reconnected via transport ${transport.socket.id}`);\n \n this.useNewTransport(transport);\n }\n catch (error)\n {\n if (error.code !== 'DCPC-1016' && error.code !== 'DCPC-1015')\n {\n /* Unreached unless there are bugs. */\n throw error;\n } \n this.close(error, true);\n }\n }\n\n connectTimer()\n {\n return new Promise((_resolve, reject) =>\n {\n var timeoutMs = (this.options.connectTimeout2 || 31) * 1000;\n \n this.connectTimeoutPromise = setTimeout(() =>\n { \n reject(new Error(`Failed to establish connection to ${this.url} within ${(timeoutMs / 1000).toFixed(1)} seconds`));\n }, timeoutMs);\n if (typeof this.connectTimeoutPromise.unref === 'function')\n this.connectTimeoutPromise.unref();\n });\n }\n \n async a$_connect()\n { \n var presharedPeerAddress, establishResults;\n var targetIdentity = await this.target.identity;\n var transport;\n \n assert(this.role === role.initiator);\n\n this.state.set('initial', 'connecting');\n do\n {\n transport = await this.a$connectToTargetTransport().catch((error) =>\n {\n debugging('connection') && console.debug(`${this.debugLabel} error connecting to target on transport layer:`, error);\n return { ready: () => {return false} };\n });\n } while(!transport.ready());\n this.adopt(transport);\n\n establishResults = await this.sender.establish().catch(error => {\n debugging('connection') && console.debug(this.debugLabel, `Could not establish DCP session ${this.transport ? 'over' + this.transport.name : '. Transport establishment was not complete'}:`, error);\n this.close(error, true);\n throw error;\n });\n const peerAddress = new wallet.Address(establishResults.peerAddress);\n const dcpsid = establishResults.dcpsid;\n debugging('connection') && console.debug(this.debugLabel, 'dcpsid is', dcpsid);\n \n if (!this.options.strict && targetIdentity && determineIfSecureConfig())\n {\n if ( false\n || typeof targetIdentity !== 'object'\n || typeof targetIdentity.address !== 'object'\n || !(targetIdentity.address instanceof wallet.Address))\n targetIdentity = { address: new wallet.Address(targetIdentity) }; /* map strings and Addresses to ks ducks */\n\n presharedPeerAddress = targetIdentity.address;\n debugging('connection') && console.debug(this.debugLabel, 'Using preshared peer address', presharedPeerAddress);\n }\n this.ensureIdentity(peerAddress, presharedPeerAddress);\n\n /* At this point, new session is valid & security checks out - make Connection instance usable */\n this.peerAddress = peerAddress;\n if (this.dcpsid)\n throw new DCPError(`Reached impossible state in connection.js; dcpsid already specified ${this.dcpsid} (${this.url})`, 'DCPC-1012');\n\n this.state.set('connecting', 'established'); /* established => dcpsid has been set */\n this.emit('session', (this.dcpsid = dcpsid));\n this.emit('connect', this.url);\n this.sender.notifyTransportReady();\n return Promise.resolve();\n }\n\n /**\n * unreference any objects entrained by this connection so that it does not prevent\n * the node program from exiting naturally.\n */\n unref()\n {\n if (this.connectAbortTimer && this.connectAbortTimer.unref && dcpEnv.platform === 'nodejs')\n this.connectAbortTimer.unref();\n }\n\n /**\n * Method is invoked when the transport disconnects. Transport instance is responsible for its own\n * finalization; Connection instance is responsible for finding a new transport, resuming the\n * connection, and retransmitting any in-flight message.\n *\n * @param {object} transport the transport instance that triggered this handler. In some cases, it\n * is possible that this event is not serviced until after the connection\n * has already acquired a new transport instance, e.g. in a Target where\n * the initiator switched networks. This implies that it is possible for\n * more 'connect' events to be emitted than 'disconnect' events.\n */\n transportDisconnectHandler(transport)\n {\n try\n { \n if (this.state.in(['disconnected', 'closing', 'close-wait', 'closed'])) /* transports may fire this more than once */\n return;\n\n if (transport !== this.transport) /* event no longer relevant */\n return;\n\n if (this.transport)\n {\n transport.close();\n delete this.transport;\n }\n \n if (this.state.is('established'))\n {\n this.state.set('established', 'disconnected');\n this.emit('disconnect', this.url); /* UI hint: \"internet unavailable\" */\n debugging('connection') && console.debug(this.debugLabel, `Transport disconnected from ${this.url}; ${this.sender.inFlight ? 'have' : 'no'} in-flight message`);\n \n if (!this.dcpsid) /* hopefully impossible? */\n {\n debugging('connection') && console.debug(this.debugLabel, 'Not reconnecting - no session');\n return;\n }\n }\n \n if (this.role === role.target)\n {\n /* targets generally can't reconnect due to NAT */\n debugging('connection') && console.debug(this.debugLabel, `Waiting for initiator to reconnect for ${this.dcpsid}`);\n return;\n }\n \n if (this.dcpsid && !this.sender.inFlight && this.options.onDemand)\n {\n debugging('connection') && console.debug(this.debugLabel, `Not reconnecting ${this.dcpsid} until next message`);\n return;\n }\n \n if (this.state.is('connecting') && (!this.dcpsid || !this.peerAddress))\n {\n debugging('connection') && console.debug(this.debugLabel, `Disconnected while connecting, establishing transport and re-sending connect request.`);\n this.a$_reconnect();\n return;\n }\n\n /* At this point, we initiate a (re)connect attempt because either\n * - we haven't connected yet,\n * - we have something to send, or\n * - we are not an on-demand connection\n */\n if (!this.state.is('connecting'))\n this.connect();\n }\n catch(error)\n {\n debugging('connection') && console.debug(this.debugLabel, 'unexpected error handling disconnect:', error);\n this.close(error, true);\n \n if (error.code !== 'DCPC-1016' && error.code !== 'DCPC-1015')\n {\n /* Unreached unless there are bugs. */\n throw error;\n }\n }\n }\n \n /**\n * Initiators only\n *\n * Connect to a target at the transport level.\n * - Rejects when we give up on all transports.\n * - Resolves with a transport instance when we connect to one.\n *\n * The connection attempt will keep a node program \"alive\" while it is happening.\n * The `autoUnref` connectionOption and unref() methods offer ways to make this not\n * happen.\n */\n async a$connectToTargetTransport()\n {\n const that = this;\n const availableTransports = [].concat(this.options.transports);\n var quitMsg = false; /* not falsey => reject asap, value is error message */\n var quitCode = undefined;\n var boSleepIntr; /* if not falsey, a function that interrupts the backoff sleep */\n var transportConnectIntr; /* if not falsey, a function that interrupts the current connection attempt */\n\n // Already trying to connect to target, don't try multiple times until we've aborted one attempt\n if (this.connectAbortTimer)\n return;\n \n /* This timer has the lifetime of the entire connection attempt. When we time out,\n * we set the quitMsg to get the retry loop to quit, then we interrupt the timer so\n * that we don't have to wait for the current backoff to expire before we notice, and\n * we expire the current attempt to connect right away as well.\n */\n this.connectAbortTimer = setTimeout(() => {\n quitMsg = 'connection timeout';\n if (boSleepIntr) boSleepIntr();\n if (transportConnectIntr) transportConnectIntr();\n }, this.options.connectTimeout * 1000);\n\n if (this.options.autoUnref)\n this.unref();\n\n /* cleanup code called on return/throw */\n function cleanup_ctt()\n {\n clearTimeout(that.connectAbortTimer);\n delete that.connectAbortTimer;\n }\n\n /* Connect to target with a specific transport. */\n /* Resolves with { bool success, obj transport } or rejects with { error } if the transport cannot connect*/\n function a$connectWithTransport(transportName)\n { \n transportConnectIntr = false;\n\n return new Promise((connectWithTransport_resolve, connectWithTransport_reject) => { \n const TransportClass = Transport.require(transportName);\n const transport = new TransportClass(that.target, that.options[transportName]);\n var ret = { transport };\n\n function cleanup_cwt()\n {\n for (let eventName of transport.eventNames())\n for (let listener of transport.listeners(eventName))\n transport.off(eventName, listener);\n }\n \n /* In the case where we have a race condition in the transport implementation, arrange things\n * so that we resolve with whatever fired last if we have a double-fire on the same pass of \n * the event loop.\n */\n transport.on('connect', () => { cleanup_cwt(); ret.success=true; connectWithTransport_resolve(ret) });\n transport.on('error', (error) => { cleanup_cwt(); connectWithTransport_reject(error) });\n transport.on('connect-failed', (error) => {\n cleanup_cwt();\n ret.success = false;\n ret.error = error;\n debugging() && console.debug(that.debugLabel, `Error connecting to ${that.url};`, error);\n connectWithTransport_resolve(ret);\n });\n \n /* let the connectAbortTimer interrupt this connect attempt */\n transportConnectIntr = () => { transport.close() };\n });\n }\n \n if (availableTransports.length === 0)\n {\n cleanup_ctt();\n return Promise.reject(new DCPError('no transports defined', 'DCPC-1014'));\n }\n \n /* Loop while trying each available transport in turn. Sleep with exponential backoff between runs */\n while (!quitMsg)\n {\n for (let transportName of availableTransports)\n {\n try\n {\n const { success, error, transport } = await a$connectWithTransport(transportName);\n\n /* Have connected to the remote at the transport level - OUT */\n if (success === true)\n {\n transportConnectIntr = false;\n cleanup_ctt();\n \n return transport;\n }\n\n /* Fast-fail: certain - but few - HTTP status codes let us know that this (or any) transport\n * will never work, so don't try those again.\n */\n if (error && error.httpStatus)\n {\n switch(error.httpStatus)\n {\n case 301: case 302: case 303: case 307: case 308:\n debugging('connection') && console.debug(this.debugLabel, `HTTP status ${error.httpStatus}; won't try again with ${transportName}`);\n availableTransports.splice(availableTransports.indexOf(transportName), 1);\n break;\n case 400: case 403: case 404:\n debugging('connection') && console.debug(this.debugLabel, `HTTP status ${error.httpStatus}; won't try again.`);\n quitMsg = error.message;\n quitCode = 'HTTP_' + error.httpStatus || 0;\n break;\n default:\n debugging('connection') && console.debug(this.debugLabel, `HTTP status ${error.httpStatus}; will try again with ${transportName}`);\n break;\n }\n }\n }\n catch (impossibleError)\n {\n /* transport connection attempts should never throw. */\n debugging('connection') && console.debug(this.debugLabel, `Error connecting to ${this.url} with ${transportName}; won't try again:`, impossibleError);\n availableTransports.splice(availableTransports.indexOf(transportName), 1);\n }\n }\n \n if (availableTransports.length === 0)\n {\n quitMsg = 'all transports exhausted';\n break;\n }\n \n /* Go to (interruptible) sleep for a while before trying again */\n const backoffTimeMs = this.backoffTimeIterator.next().value;\n debugging('connection') && console.debug(this.debugLabel, 'trying again in', Number(backoffTimeMs / 1000).toFixed(2), 'seconds');\n const boSleepPromise = a$sleepMs(backoffTimeMs);\n boSleepIntr = boSleepPromise.intr;\n await boSleepPromise;\n boSleepIntr = false;\n }\n\n /* The only way we get here is for us to discover that the connection is unconnectable - eg \n * reject timer has expired or similar.\n */\n cleanup_ctt();\n throw new DCPError(quitMsg, 'DCPC-1016', quitCode);\n }\n\n /**\n * Method which must be invoked whenever a new transport needs to be assigned to the connection. \n * If we have previously adopted a transport, we close it first, which will prevent \n * [by transport definition] any message handlers from firing, even if the old transport instance\n * has buffered traffic.\n *\n * @param {object} transport transport instance\n */\n adopt(transport)\n {\n if (this.transport)\n this.transport.close();\n\n transport.on('message', (m) => this.handleMessage(m));\n transport.on('end', () => this.transportDisconnectHandler(transport));\n transport.on('close', () => this.transportDisconnectHandler(transport));\n \n this.transport = transport;\n }\n \n \n /**\n * Method that gets invoked when there is a new transport available for adoption.\n * This will adjust the state of the connection, adopt the transport then tell\n * the sender to pump the message queue.\n * @param {object} transport transport instance \n */\n useNewTransport(transport)\n {\n if (this.state.in(['closing', 'close-wait', 'closed']))\n {\n debugging('connection') && console.debug(`${this.debugLabel} got a new transport during closing. closing the new transport and not completing transport adoption.`)\n transport.close();\n return;\n }\n \n var preDisconnectState = (!this.dcpsid || !this.peerAddress) ? 'connecting' : 'established';\n if (this.state.isNot(preDisconnectState))\n this.state.set(['connecting', 'disconnected'], preDisconnectState);\n this.adopt(transport);\n this.emit('connect', this.url); // UI hint: \"internet available\" \n this.sender.notifyTransportReady();\n }\n \n /**\n * Method that must be invoked by the target to \"accept\" a new DCP Connection request\n * at the transport layer.\n * @param {object} transport \n */\n async accept(transport)\n {\n assert(!this.role);\n await this.a$assumeRole(role.target);\n this.state.set('initial', 'connecting');\n this.adopt(transport);\n }\n \n /**\n * This method is invoked by the target when it has handled the initial connect request from\n * the initiator, which contains the peerAddress and the first half of the dcpsid (the second half is\n * populated by receiver.handleFirstRequest before being passed here). It transitions the connection \n * into an established state at the protocol level.\n * Note - this is really not the right design for this, but it is invoked from handleFirstRequest\n * in ./receiver.js\n *\n * @param {string} dcpsid dcpsid\n * @param {wallet.Address} peerAddress Address of peer\n */\n establishTarget(dcpsid, peerAddress) {\n assert(this.role === role.target);\n \n this.connectResponseId = Symbol('dummy'); // un-register ConnectResponse\n this.peerAddress = peerAddress;\n if (this.dcpsid)\n throw new DCPError(`Reached impossible state in connection.js; dcpsid already specified ${this.dcpsid}!=${dcpsid} (${this.url})`, 'DCPC-1005');\n this.emit('session', (this.dcpsid = dcpsid));\n debugging() && console.debug(this.debugLabel, 'dcpsid is', dcpsid);\n\n this.loggableDest = this.role === role.initiator ? this.url : peerAddress;\n this.state.set('connecting', 'established'); /* established => dcpsid has been set */\n\n debugging('connection') && console.debug(this.debugLabel, `Established session ${this.dcpsid} with ${this.peerAddress} for ${this.url}`);\n }\n\n /**\n * Check to see if the peer address conflicts with what we have in the global identity cache;\n * it does, throw an exception.\n */\n ensureIdentity (peerAddress, presharedPeerAddress)\n {\n let idc = getGlobalIdentityCache();\n let noConflict = idc.learnIdentity(this.url, peerAddress, presharedPeerAddress);\n\n if (!noConflict)\n throw new DCPError(`**** Security Error: Identity address ${peerAddress} does not match the saved key for ${this.url}`, 'DCPC-EADDRCHANGE');\n }\n \n \n /**\n * This method uses the first request (if we're target) or ack to the first request \n * (if we're initiator) to memoize the address of the peer authorized to send to us. \n * The first message must only be a request (since it comes from sender.specialFirstSend)\n * that has a connect operation, or an acknowledgement of the first request. \n * All future messages' owners will be validated against this authorized sender.\n * @param {Object} message \n */\n setAuthorizedSender(message)\n {\n if (message.body.type !== 'request' && message.body.type !== 'ack')\n throw new DCPError('First protocol message was not a request or ack', 'DCPC-1017');\n \n if (message.body.type === 'request' && message.body.payload.operation !== 'connect')\n throw new DCPError('First protocol message did not contain the correct payload', 'DCPC-1017');\n \n if (message.body.type === 'ack' && this.sender.inFlight.message.payload.operation !== 'connect')\n throw new DCPError('First protocol acknowledgement was not for connect request', 'DCPC-1017');\n \n this.authorizedSender = message.owner;\n }\n \n /**\n * Emits an error event with the relevant error and closes the connection immediately.\n * @param {string} errorMessage \n * @param {string} errorCode \n */\n \n handleValidationError(errorMessage, errorCode)\n {\n var messageError;\n \n debugging('connection') && console.debug(this.debugLabel, 'Message failed validation -', errorMessage);\n this.emit('error', (messageError = new DCPError(`message failed validation: ${errorMessage}`, errorCode)))\n this.close(messageError, true);\n }\n\n /**\n * This method validates the message owner, signature and body before passing it onto\n * either the receiver (for a request, response or batch) or the messageLedger (for an ack).\n * If it's a request, response or batch, this method also provokes the connection to \n * send an acknowledgement (ack) to the peer to let them know we got their message.\n * XXXwg this code needs an audit re error handling: what message error should we be emitting?\n * why do we keep working after we find an error?\n * XXXsc did some auditing. we happy now?\n * @param {string} JSON-encoded unvalidated message object\n */\n async handleMessage (messageJSON) {\n var validation;\n var message;\n\n if (this.state.is('closed')) {\n debugging('connection') && console.debug(this.debugLabel, 'handleMessage was called on a closed connection.');\n return;\n }\n\n try\n {\n message = typeof messageJSON === 'object' ? messageJSON : JSON.parse(messageJSON);\n debugging('wire') && console.debug(this.debugLabel, `handleMessage: ${String(message && message.body && message.body.type).padEnd(10, ' ')} <- ${this.loggableDest}`);\n }\n catch(error)\n {\n console.error('connection::handleMessage received unparseable message from peer:', error);\n this.emit('error', error);\n return;\n }\n \n /**\n * We always ack a duplicate transmission.\n * This must happen before validation since during startup we may lack a\n * nonce or dcpsid (depending on whether initiator or target + race).\n */\n if (this.isDuplicateTransmission(message)) {\n debugging('connection') && console.debug(this.debugLabel, 'duplicate message:', message.body);\n debugging('wire') && console.debug(this.debugLabel, `dup message ack: ${String(message.body.type).padEnd(10, ' ')} -> ${this.loggableDest}`);\n\n this.sendAck(this.lastAckSigned) \n return;\n }\n\n debugging('connection') && console.debug(this.debugLabel, `received message ${message.body.type} ${message.body.id}; nonce=`, message.body.nonce);\n \n validation = this.validateMessageDCPSID(message);\n if (validation.success !== true)\n {\n this.handleValidationError(validation.errorMessage, 'ENODCPSID');\n return;\n }\n\n validation = this.validateMessageOwner(message)\n if (validation.success !== true)\n {\n this.handleValidationError(validation.errorMessage, 'EINVAL');\n return;\n }\n \n validation = this.validateMessageSignature(message);\n if (validation.success !== true)\n {\n this.handleValidationError(validation.errorMessage, 'EINVAL');\n return;\n }\n\n validation = this.validateMessageBody(message);\n if (validation.success !== true)\n {\n this.handleValidationError(validation.errorMessage, validation.errorCode || 'EINVAL'); /* messages of type 'unhandled-message' may contain more information about the failure */\n return;\n }\n \n if (message.body.type === \"ack\") {\n const ack = new this.Ack(message.body);\n this.messageLedger.handleAck(ack);\n return;\n } else if (message.body.type !== 'unhandled-message') {\n this.lastMessage = message;\n await this.ackMessage(message);\n }\n \n this.receiver.handleMessage(message);\n }\n\n \n /**\n * This method takes either a Request, Response or Batch, creates an ack for it\n * and sends it to the peer. This ack contains the nonce we expect on the next\n * message from peer.\n * @param {Connection.Message} message \n */\n async ackMessage(message) {\n debugging('connection') && console.debug(this.debugLabel, 'acking message of type: ', message.body.type);\n const ack = new this.Ack(message);\n const signedMessage = await ack.sign(this.identity);\n\n debugging('wire') && console.debug(this.debugLabel, `ackMessage: ${String(message.body.type).padEnd(10, ' ')} -> ${this.loggableDest}`);\n\n this.sendAck(signedMessage);\n this.lastAck = ack;\n this.lastAckSigned = signedMessage;\n }\n\n /**\n * Checks if the batch we just received has the same nonce\n * as the most-recently received batch.\n * @param {object} messageJSON\n */\n isDuplicateTransmission(messageJSON) {\n return this.lastMessage && this.lastMessage.body.nonce && this.lastMessage.body.nonce === messageJSON.body.nonce;\n }\n\n /**\n * Validate that the message came from the appropriate sender.\n * @param {Object} message the message to validate\n * @returns {Object} returns an object `ret` with either ret.success = true, \n * or ret.success = false accompanied by another property ret.errorMessage\n */\n validateMessageOwner(message)\n {\n if (!this.authorizedSender)\n {\n /* Capture the initial identity of the remote end during the connect operation */\n this.setAuthorizedSender(message);\n return { success: true }\n }\n else if (message.owner !== this.authorizedSender)\n {\n return { success: false, errorMessage: \"message came from unauthorized sender\" }\n }\n return { success: true }\n }\n \n /**\n * Validate that the signature was generated from this message body\n * @param {Object} message\n * @returns {Object} with properties 'success' and 'errorMessage'. When the message is valid on its \n * face, the success property is true, otherwise it is is false. When it is false,\n * the errorMessage property will be a string explaining why.\n */\n validateMessageSignature(message)\n {\n if (!message.signature) {\n debugging('connection') && console.warn(this.debugLabel, \"Message does not have signature, aborting connection\");\n return { success: false, errorMessage: \"message is missing signature\" };\n }\n \n const owner = new wallet.Address(message.owner);\n const signatureValid = owner.verifySignature(message.body, message.signature);\n\n if (!signatureValid)\n {\n debugging('connection') && console.warn(this.debugLabel, \"Message has an invalid signature, aborting connection\");\n return { success: false, errorMessage: \"invalid message signature\" };\n }\n\n return { success: true };\n }\n \n validateMessageDCPSID(message)\n {\n if (this.dcpsid !== null && message.dcpsid)\n {\n if (message.dcpsid !== this.dcpsid)\n {\n debugging('connection') && console.warn(this.debugLabel, 'Message has an invalid dcpsid, aborting connection');\n return { success: false, errorMessage: 'message has an invalid dcpsid' };\n }\n }\n \n return { success: true };\n }\n /**\n * This method is used to perform validation on all types of messages.\n * It validates the DCPSID, nonce, and the peerAddress.\n * @param {Object} message\n * @returns {Object} with properties 'success' and 'errorMessage'. When the message is valid on its \n * face, the success property is true, otherwise it is is false. When it is false,\n * the errorMessage property will be a string explaining why.\n *\n */\n validateMessageBody(message)\n {\n try\n {\n if (message.body.type === 'unhandled-message')\n {\n /* This special message type may not have a dcpsid, peerAddress, etc., so it might not\n * validate. It is also not a \"real\" message and only used to report ConnectionManager routing \n * errors, so we just report here, drop it, and close the connection.\n *\n * Note also that this is probably the wrong way to handle this case - restarting daemons - but\n * that is a problem for another day. /wg nov 2021\n */\n debugging('connection') && console.warn(this.debugLabel, \"Target Error - target could not process message.\", JSON.stringify(message.body),\n \"Aborting connection.\");\n return { success: false, errorMessage: `target could not process message (${message.body.payload && message.body.payload.message || 'unknown error'})`, errorCode: message.body.payload && message.body.payload.code}\n }\n if (this.peerAddress && !this.peerAddress.eq(message.owner))\n {\n debugging('connection') && console.warn(this.debugLabel,\n \"Received message's owner address does not match peer address, aborting connection\\n\",\n \"(owner addr)\", message.owner, '\\n',\n \"(peer addr)\", this.peerAddress);\n return { success: false, errorMessage: \"received message owner does not match peer address\" };\n }\n\n if (this.state.in(['established', 'closing', 'close-wait']) && message.body.type !== 'unhandled-message')\n {\n const body = message.body;\n\n assert(this.peerAddress); /* should be set in connect */\n /**\n * Security note:\n * We don't require the dcpsid to match on an ack because the connect response\n * ack doesn't have a dcpsid until after it is processed. Also ack's are protected\n * by ack tokens and signatures, so this doesn't leave a hole, just an inconsistency.\n */\n if (body.type !== 'ack' && body.dcpsid !== this.dcpsid)\n {\n debugging('connection') && console.warn(this.debugLabel,\n \"Received message's DCPSID does not match, aborting connection\\n\",\n \"Message owner:\", message.owner, '\\n',\n \"(ours)\", this.dcpsid, (Date.now() - this.connectTime)/1000, \"seconds after connecting - state:\", this.state._, \"\\n\", \n \"(theirs)\", body.dcpsid);\n if(body.dcpsid.substring(0, body.dcpsid.length/2) !== this.dcpsid.substring(0, this.dcpsid.length/2)){\n debugging('connection') && console.warn(this.debugLabel, \" Left half of both DCPSID is different\");\n }\n if(body.dcpsid.substring(body.dcpsid.length/2 + 1, body.dcpsid.length) !== this.dcpsid.substring(this.dcpsid.length/2 + 1, body.dcpsid.length)){\n debugging('connection') && console.warn(this.debugLabel, \" Right half of both DCPSID is different\");\n }\n return { success: false, errorMessage: \"DCPSID do not match\" };\n }\n /* can get close in middle of connecting, which will have no nonce.*/\n if (body.type !== 'ack' && this.lastAck.nonce !== body.nonce && (body.payload && body.payload.operation !== 'close'))\n {\n /* When Target sends back ConnectionLessErrorResponse, it uses the nonce of the message that caused an error. */\n if (this.sender.inFlight && this.sender.inFlight.message.nonce === body.nonce)\n {\n debugging('connection') && console.debug(`${this.debugLabel} Received messages nonce matches nonce of our current inFlight message.`,\n \"There was a problem sending this message. Aborting connection. Reason:\\n\", body.payload);\n return { success: false, errorMessage: \"current inflight message returned an error\" }\n }\n debugging('connection') && console.warn(this.debugLabel,\"Received message's nonce does not match expected nonce, aborting connection\\n\");\n debugging('connection') && console.debug(this.debugLabel, this.lastAck.nonce, body.nonce);\n return { success: false, errorMessage: \"received message's nonce does not match expected nonce\" };\n }\n if (body.type === 'request') \n {\n if (body.payload.validity.time === undefined)\n return { success: false, errorMessage: 'received message does not have a valid time in its payload' };\n }\n }\n\n return { success: true };\n }\n catch(error)\n {\n console.error('message validator failure:', error);\n return { success: false, errorMessage: 'validator exception ' + error.message };\n }\n\n return { success: false, errorMessage: 'impossible code reached' }; // eslint-disable-line no-unreachable\n }\n\n /**\n * Targets Only.\n * The receiver creates a special connect response and the connection\n * needs to know about it to get ready for the ack. See `isWaitingForAck`.\n * @param {Message} message message we are sending out and waiting to\n * ack'd, probably a batch containing the response.\n */\n registerConnectResponse(message) {\n this.connectResponseId = message.id;\n }\n\n /**\n * Targets only\n * During the connection process a target sends a connect\n * response to an initiator and the initiator will ack it. Since transports\n * are not tightly coupled, we have no authoritative way to route the ack back\n * to the right connection. So a connection briefly registers the ack it\n * is looking for in this case. It will formally validate the ack after routing.\n * @param {string} messageId id of the message this ack is acknowledging.\n */\n isWaitingForAck(messageId) {\n return messageId === this.connectResponseId;\n }\n\n /**\n * Put connection into close-wait state so that a call to `close`\n * in this state will *not* trigger sending a `close` message to the peer.\n * Then call close.\n *\n * @note: This function is called when the remote end of the transport sends\n * a close command, from receiver::handleOperation. This impllies that\n * that we must be in established or later state.\n */\n closeWait (errorCode = null)\n {\n var preCloseState, reason;\n \n debugging('connection') && console.debug(this.debugLabel, `responding to close. state=closeWait dcpsid=${this.dcpsid}`);\n\n if (this.state.is('closed'))\n {\n debugging('connection') && console.debug(this.debugLabel, `remote asked us to close a closed connection; dcpsid=${this.dcpsid}`);\n return;\n }\n\n // continue with close in either case\n reason = `Received close from peer with Error Code ${errorCode}`;\n if (this.role === role.target)\n reason += ` (${this.url})`;\n else\n reason += ` (${this.debugLabel}${this.peerAddress.address})`;\n\n reason = new DCPError(reason, errorCode || 'DCPC-1011');\n\n // If we're already closing, wait for it to complete then resolve\n // WARNING: any place we transition to closing or close-wait, we MUST guarantedd\n // that 'end' will be emitted, or this code will hang forever!\n if (this.state.in(['close-wait', 'closing'])) {\n return new Promise((resolve) => {\n this.once('end', resolve) /* eventually fired by doClose elsewhere */\n });\n }\n\n /* XXXwg - this should only be established->close-wait. Why more? */\n this.state.set(['disconnected', 'connecting', 'established'], 'close-wait');\n \n /* Set preCloseState to close-wait so doClose doesn't send a close message back */\n preCloseState = this.state.valueOf();\n return this.doClose(preCloseState, reason, true);\n }\n\n /**\n * This method will begin closing the protocol connection. It transitions\n * the protocol into the correct state, and then begins the work of closing.\n * \n * @param {string|Error} [reason] Either an Error or a message to use in the Error that will reject pending sends.\n * @param {boolean} [immediate] When true, the connection will not deliver any pending messages and instead\n * immediately send the peer a 'close' request. \n *\n * @return a Promise which resolves when the connection has been confirmed closed and the end event has been fired.\n */\n close (reason='requested', immediate=false)\n {\n if (this.state.is('initial'))\n {\n debugging('connection') && console.debug(this.debugLabel, 'not yet established; closing directly.');\n this.state.set('initial', 'closed');\n this.emit('close'); /* Don't emit dcpsid */\n }\n if (this.state.is('closed')) return Promise.resolve();\n\n const preCloseState = this.state.valueOf();\n debugging('connection') && \n console.debug(this.debugLabel, \n `close; dcpsid=${this.dcpsid} state=${preCloseState} immediate=${immediate} reason:`, reason);\n\n // If we're already closing, wait for it to complete then resolve\n if (this.state.in(['close-wait', 'closing'])) {\n debugging('connection') && console.debug(this.debugLabel, 'already closing; awaiting `end` event...');\n return new Promise((resolve) => {\n this.once('end', resolve)\n });\n }\n\n this.state.set(['connecting', 'established', 'disconnected'], 'closing');\n\n // Perform actual work of closing\n return this.doClose(preCloseState, reason, immediate);\n }\n\n /**\n * Sends close message to peer after sending all pending messages.\n * Note that close messages are sent without the expectation of a response.\n * @param {DCPError|string} reason reason for closing\n */\n async sendCloseGracefully(reason) \n {\n debugging('connection') && console.debug(`${this.debugLabel} gracefully sending close message to peer with reason ${reason}`)\n let errorCode = reason instanceof DCPError ? reason.code : 'DCPC-1011';\n \n /* This only resolves when close is next message in queue */\n const closeMessage = await this.prepare('close', { errorCode: errorCode });\n debugging('connection') && console.debug(`1249: close message prepared; sending...`);\n this.sendPreparedMessage(closeMessage);\n this.messageLedger.fulfillMessagePromise(closeMessage.message.id, {});\n debugging('connection') && console.debug(`1251: close message prepared; sending...`);\n }\n \n /**\n * Sends close message to peer immediately. Pending messages will not be sent.\n * Note that close messages are sent without expectation of response.\n * @param {DCPError|string} reason reason for closing\n */\n async sendCloseImmediately(reason)\n {\n debugging('connection') && console.debug(`${this.debugLabel} immediately sending close message to peer with reason ${reason}`);\n let errorCode = reason instanceof DCPError ? reason.code : 'DCPC-1011';\n \n /* Last param being `true` means that prepareMessage will return unsigned message. Does not queue message. */\n const closeMessage = await this.prepare('close', { errorCode: errorCode }, true);\n \n if (this.sender.inFlight)\n closeMessage.nonce = this.sender.inFlight.message.nonce;\n else\n closeMessage.nonce = this.sender.nonce;\n \n let signedCloseMessage = await closeMessage.sign();\n \n /* Overwrite the in-flight message because we don't care to deliver pending messages */\n this.sender.inFlight = { message: closeMessage, signedMessage: signedCloseMessage };\n this.sender.sendInFlightMessage();\n }\n \n /**\n * This method performs the core close functionality. It appropriately sends the close message\n * to the peer, fails any pending transmissions, shuts down our sender and underlying transport\n * and puts us into the 'closed' state, indicating this connection object is now useless.\n * When called from closeWait, it does not send a close message.\n * @param {string} preCloseState the state that the connection was in at the start of the\n * invocation of close() or closeWait()\n *\n * @note: this function is not reentrant due to closeGracefully\n */\n async doClose(preCloseState, reason, immediate) {\n const dcpsid = this.dcpsid;\n var rejectErr;\n\n try\n {\n // Emit the close event the moment we know we are going to close, \n // so we can catch the close event and reopen the connection\n //\n // This implies that no API functions which call doClose may await between\n // their invocation and their call to doClose!\n this.emit('close', dcpsid /* should be undefined in initial state */);\n\n assert(this.state.in(['closing', 'close-wait']));\n if (preCloseState === 'established' && this.transport) {\n try {\n if (immediate) {\n await this.sendCloseImmediately(reason);\n } else {\n await this.sendCloseGracefully(reason);\n }\n } catch(e) {\n debugging() && console.warn(this.debugLabel, `Warning: could not send close message to peer. connectionid=${this.id}, dcpsid=,${this.dcpsid}, url=${this.url ? this.url.href : 'unknown url'} - (${e.message})`);\n }\n }\n\n // can delete these now that we've sent the close message\n this.dcpsid = null;\n this.peerAddress = null;\n\n if (reason instanceof DCPError)\n rejectErr = reason;\n else\n {\n let errorMessage = reason instanceof Error ? reason : `Connection to ${this.loggableDest} closed (${reason})`;\n rejectErr = new DCPError(errorMessage, 'DCPC-1013');\n }\n \n // Reject any pending transmissions in the message ledger\n this.messageLedger.failAllTransmissions(rejectErr);\n \n if (this.transport)\n {\n try { this.sender.shutdown(); }\n catch(e) { debugging() && console.warn(this.debugLabel, `Warning: could not shutdown sender; dcpsid=,${dcpsid}`, e); }\n \n try { this.transport.close(); delete this.transport; }\n catch(e) { debugging() && console.warn(this.debugLabel, `Warning: could not close transport; dcpsid=,${dcpsid}`, e); }\n }\n } catch(error) {\n debugging() && console.warn(this.debugLabel, `could not close connection; dcpsid=${dcpsid}, url=${this.url ? this.url.href : 'unknown url'}:`, error);\n }\n finally\n {\n this.state.set(['closing', 'close-wait'], 'closed');\n this.emit('end'); /* end event resolves promises on other threads for closeWait and close (ugh) */\n }\n }\n/**\n * Prepares a non-batchable message that can be sent directly over the wire. Returns when\n * the message has been signed and is ready to be sent. The connection will not be able to send \n * any messages until the prepared message here is either sent or discarded. If 'canBatch = true',\n * will return the unsigned message instead. In this case, enqueuing is handled by\n * `async Connection.send()`, allowing the message to be put in a batch before being signed.\n * @param {...any} messageData Data to build message with. Format is:\n * `operation {string}, \n * data {Object} (optional),\n * identity {wallet.Keystore} (optional),\n * canBatch {boolean} (optional)`\n * @returns {Promise<Object>} a promise which resolves to { message, signedMessage }\n */\n\n async prepare(...messageData)\n {\n if (this.state.isNot('established'))\n {\n await this.connect().catch((e) => {\n if (e.code !== 'DCPC-1015') /* If we're closed already, then swallow the error */\n { \n this.close(e, true);\n throw e;\n }\n });\n }\n \n \n let signedMessage, message = messageData[0];\n let canBatch = false;\n \n if (typeof messageData[messageData.length - 1] === 'boolean')\n canBatch = messageData.pop();\n \n if (!message.id)\n {\n message = this.Request.buildMessage(...messageData);\n }\n \n debugging('connection') && console.debug(`${this.debugLabel} Created message ${message.id}.`);\n \n message.ackToken = this.sender.makeAckToken();\n message.batchable = canBatch;\n \n if (canBatch)\n return Promise.resolve(message);\n \n debugging('connection') && console.debug(`${this.debugLabel} Preparing message ${message.id} for sending...`); \n const messageWithNonce = await new Promise((resolve) =>\n {\n // This event is fired in the sender by serviceQueue() when the message is at the top of the queue\n // and has a nonce it can sign with. At this point, we may return the prepared message.\n this.once(`${message.id} ready`, (message) => resolve(message))\n \n // if we're enqueing a graceful close message, delay until one pass of the event loop so we can handle pending messages \n if (message.payload.operation === 'close')\n setImmediate(() => { \n this.sender.queue.push(message);\n this.sender.requestQueueService();\n }); \n else\n this.sender.queue.push(message)\n \n this.sender.requestQueueService();\n })\n \n signedMessage = await messageWithNonce.sign();\n \n debugging('connection') && console.debug(`${this.debugLabel} Finished preparing message. ${message.id} is ready to be sent.`);\n \n return { message: messageWithNonce, signedMessage: signedMessage };\n }\n\n /**\n * Sends a message to the connected peer. If the connection has not yet been established,\n * this routine will first invoke this.connect(). If the first argument has a 'signedMessage'\n * property, the message is assumed to be prepared and is sent immediately. If not, and the first\n * argument does not have an 'id' property, it will be sent to `async prepare()`, and then put\n * in the message queue.\n * \n * @param {...any} args 3 forms:\n * [operation]\n * [operation, data]\n * [operation, data, identity]\n * @returns {Promise<Response>} a promise which resolves to a response.\n */\n async send(...args)\n {\n if (!this.state.is('established'))\n await this.connect().catch((e) =>\n {\n if (e.code !== 'DCPC-1015') /* If we're closed already, then swallow the error */\n { \n this.close(e, true);\n throw e;\n }\n });\n\n let message = args[0];\n // ie. already prepared\n if (message.signedMessage)\n return this.sendPreparedMessage(message);\n \n // ie. message not hyrdated or is a response, which needs ack token\n if (!message.id || message.type === 'response')\n message = await this.prepare(...args, true);\n\n if (this.state.in(['closed']))\n throw new DCPError(`Connection (${this.id}) is ${this.state}; cannot send. (${this.loggableDest})`, 'DCPC-1001');\n \n return this.sender.enqueue(message);\n }\n \n /**\n * Set the sender's flight deck with the given message and send it.\n * Can only be passed a prepared message, which is a promise that only\n * resolves to a message when it is signed with the nonce, so it must\n * be the next message to be sent (or discarded).\n * @param {Object} messageObject\n * @returns {Promise<Response>} \n */\n sendPreparedMessage(messageObject)\n {\n if (!messageObject.signedMessage) return;\n \n const { message, signedMessage } = messageObject;\n assert(!this.sender.inFlight);\n this.sender.inFlight = { message: message, signedMessage: signedMessage };\n const messageSentPromise = this.messageLedger.addMessage(message);\n this.sender.sendInFlightMessage();\n \n return messageSentPromise;\n }\n \n /**\n * Send a signed ack directly over the wire. If we get a SocketIO.Send: Not Connected error, \n * wait until we're connected and then resend the ack.\n * @param {String} ack \n */\n sendAck(ack)\n {\n try\n {\n this.transport.send(ack)\n }\n catch(error)\n {\n // Transport was lost\n if (error.code === 'DCPC-1105')\n this.once('connect', () => this.sendAck(ack));\n else\n console.error(`${this.debugLabel} Error acking message to ${this.loggableDest}: ${error}`);\n }\n }\n \n /**\n * Discard a prepared message by removing it from the queue.\n * Returns nonce to sender and provokes queue service.\n * @param {Object} messageObject { message, signedMessage } message to discard \n */\n discardMessage(messageObject)\n {\n let { message } = messageObject;\n this.sender.nonce = message.nonce;\n delete message.nonce;\n message.type = 'unhandled-message';\n this.sender.requestQueueService();\n }\n\n /**\n * This routine returns the current time for the purposes of\n * populating the Request message payload.validity.time property.\n * \n * @returns {Number} the integer number of seconds which have elapsed since the epoch\n */\n currentTime() {\n let msSinceEpoch;\n if (this.hasNtp) {\n msSinceEpoch = Date.now();\n } else {\n const msSinceLastReceipt = performance.now() - this.receiver.lastResponseTiming.receivedMs;\n msSinceEpoch = this.receiver.lastResponseTiming.time * 1000 + msSinceLastReceipt;\n }\n return Math.floor(msSinceEpoch / 1000);\n }\n\n /**\n * This method sends a keepalive to the peer, and resolves when the response has been received.\n */\n keepalive() {\n return this.send('keepalive');\n }\n}\n\n/** \n * Determine if we got the scheduler config from a secure source, eg https or local disk.\n * We assume tha all https transactions have PKI-CA verified.\n *\n * @note protocol::getSchedulerConfigLocation() is populated via node-libs/config.js or dcp-client/index.js\n *\n * @returns true or falsey\n */\nfunction determineIfSecureConfig()\n{\n var schedulerConfigLocation = (__webpack_require__(/*! dcp/protocol-v4 */ \"./src/protocol-v4/index.js\").getSchedulerConfigLocation)();\n var schedulerConfigSecure;\n\n if (schedulerConfigLocation && (schedulerConfigLocation.protocol === 'https:' || schedulerConfigLocation.protocol === 'file:'))\n {\n debugging('strict-mode') && console.debug(`connection: scheduler config location ${schedulerConfigLocation} is secure`); /* from casual eavesdropping */\n schedulerConfigSecure = true;\n }\n\n if (isDebugBuild)\n {\n debugging('strict-mode') && console.debug('connection: scheduler config location is always secure for debug builds');\n schedulerConfigSecure = 'debug';\n }\n\n debugging('strict-mode') && console.debug(`connection: Config Location ${schedulerConfigLocation} is ${!schedulerConfigSecure ? 'not secure' : 'secure-' + schedulerConfigSecure}`);\n return schedulerConfigSecure;\n}\n\n/**\n * Determine if a URL is secure by examinining the protocol, connection, and information about the \n * process; in particular, we try to determine if the dcp config was securely provided, because if \n * it wasn't, then we can't have a secure location, since the origin could be compromised.\n * \n * \"Secure\" in this case means \"secure against casual eavesdropping\", and this information should only\n * be used to refuse to send secrets over the transport or similar.\n *\n * @returns true or falsey\n */\nfunction determineIfSecureLocation(conn)\n{\n var isSecureConfig = determineIfSecureConfig();\n var secureLocation;\n\n if (!isSecureConfig) /* can't have a secure location without a secure configuration */\n return null;\n \n if (isDebugBuild || conn.url.protocol === 'https:' || conn.url.protocol === 'tcps:')\n secureLocation = true;\n else if (conn.role === role.initiator && conn.target.hasOwnProperty('friendLocation') && conn.url === conn.target.friendLocation)\n secureLocation = true;\n else if (conn.options.allowUnencryptedSecrets)\n secureLocation = 'override';\n else\n secureLocation = false;\n\n debugging('strict-mode') && console.debug(`connection: Location ${conn.url} is ${!secureLocation ? 'not secure' : 'secure-' + secureLocation}`);\n \n return secureLocation;\n}\nexports.Connection = Connection;\n\n\n//# sourceURL=webpack://dcp/./src/protocol-v4/connection/connection.js?");
|
|
4698
|
+
eval("/* provided dependency */ var process = __webpack_require__(/*! ./node_modules/process/browser.js */ \"./node_modules/process/browser.js\");\n/**\n * @file protocol/connection/connection.js\n * @author Ryan Rossiter\n * @author KC Erb\n * @author Wes Garland\n * @date January 2020, Feb 2021, Mar 2022\n *\n * A Connection object represents a connection to another DCP entity. \n * A DCP connection may 'live' longer than the underlying protocol's connection,\n * and the underlying protocol connection (or, indeed, protocol) may change\n * throughout the life of the DCP connection.\n * \n * DCP connections are uniquely identified by the DCP Session ID, specified by\n * the dcpsid property, present in every message body. This session id negotiated during connection,\n * with the initiator and target each providing half of the string.\n *\n * Connection instance events:\n * - session: dcpsid new session established\n * - connect: url UI hint: internet available\n * - disconnect: url UI hint: internet not available\n * - readyStateChange: *** DO NOT USE **\n * - error: error emitted when errors happen that would otherwise go uncaught\n * - close: connection instance is closing\n * - end: Connection instance is closed\n * - send: msgObj when a message is sent to the peer; does not wait for ack; may re-trigger on reconnect\n * - ready: when the connection is ready for traffic (constructor promises resolved)\n *\n * State Transition Diagram for Connection.state:\n *\n * initial connecting established disconnected close-wait closing closed\n * ===========================================================================================================================\n * |-- i:connect ---->\n * |-- t:accept ----->\n * |-- t:establishTarget -->\n * |-- i:connect ---------->\n * |-- transportDisconnectHandler -->\n * <-- i:_reconnect ----------------------------------------|\n * |-i:useNewTransport-->\n * <-- t:useNewTransport --------|\n * |-- closeWait ----------------------------------------------------------->\n * |-- closeWait ----------------------------------->\n * |-- closeWait -->\n * |-- doClose --------------->\n * |-- close ------------------------------------------------------------------------------------------------------------> \n * |-- close ---------------------------------------------------------------------------->\n * |-- close ---------------------------------------------------->\n * |-- close ------------------->\n * |-- doClose -->\n *\n *\n * Not until the established state can we count on things like a dcpsid, \n * peerAddress, identityPromise resolution and so on.\n * \n * Error Codes relevant to DCP Connections:\n * DCPC-1001 - CONNECTION CANNOT SEND WHEN IN CLOSING, CLOSE-WAIT OR CLOSED\n * EINVAL - MESSAGE OWNER IS INVALID (formerly DCPC-1002)\n * MESSAGE SIGNATURE INVALID (formerly DCPC-1003)\n * MESSAGE BODY IS INVALID (formerly DCPC-1004)\n * DCPC-1005 - TRYING TO ESTABLISH TARGET AFTER TARGET ALREADY ESTABLISHED\n * DCPC-1006 - CONNECTION COULD NOT BE ESTABLISHED WITHIN 30 SECONDS\n * DCPC-1007 - RECEIVED MESSAGE PAYLOAD BEFORE CONNECT OPERATION\n * DCPC-1008 - TARGET RESPONDED WITH INVALID DCPSID\n * DCPC-1009 - MESSAGE IS OF UNKNOWN TYPE\n * DCPC-1010 - DUPLICATE TRANSMISSION RECEIPT\n * DCPC-1011 - DEFAULT ERROR CODE WHEN PEER SENDS CLOSE MESSAGE\n * DCPC-1012 - TRIED TO INITIATE CONNECTION AFTER SESSION ALREADY ESTABLISHED\n * DCPC-1013 - DEFAULT ERROR CODE WHEN CLOSING WITH REASON THATS NOT INSTANCE OF DCPERROR\n * DCPC-1014 - NO TRANSPORTS AVAILABLE\n * DCPC-1015 - CANNOT CONNECT WHEN CONNECTION ALREADY CLOSED\n * DCPC-1016 - ERROR CONNECTING VIA AVAILABLE TRANSPORTS\n * DCPC-1017 - FIRST PROTOCOL MESSAGE WAS DID NOT INVOLVE INITIAL CONNECT REQUEST\n * DCPC-1018 - INVALID ARGUMENT PROVIDED IN PLACE OF IDKEYSTORE\n * ENODCPSID - CONNECTION INSTANCE TRIED TO RE-CONNECT TO A TARGET WHICH DOES NOT HAVE A RECORD OF THAT SESSION\n */\n\n\n\nconst debugging = (__webpack_require__(/*! dcp/debugging */ \"./src/debugging.js\").scope)('dcp');\nconst dcpEnv = __webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\");\nconst { EventEmitter } = __webpack_require__(/*! dcp/common/dcp-events */ \"./src/common/dcp-events/index.js\");\nconst wallet = __webpack_require__(/*! dcp/dcp-client/wallet */ \"./src/dcp-client/wallet/index.js\");\nconst { DcpURL } = __webpack_require__(/*! dcp/common/dcp-url */ \"./src/common/dcp-url.js\");\nconst { requireNative } = __webpack_require__(/*! dcp/dcp-client/webpack-native-bridge */ \"./src/dcp-client/webpack-native-bridge.js\");\nconst { assert } = __webpack_require__(/*! dcp/common/dcp-assert */ \"./src/common/dcp-assert.js\");\nconst { leafMerge, a$sleepMs } = __webpack_require__(/*! dcp/utils */ \"./src/utils/index.js\");\nconst { Synchronizer } = __webpack_require__(/*! dcp/common/concurrency */ \"./src/common/concurrency.js\");\nconst { DCPError } = __webpack_require__(/*! dcp/common/dcp-error */ \"./src/common/dcp-error.js\");\n\nconst { Transport } = __webpack_require__(/*! ../transport */ \"./src/protocol-v4/transport/index.js\");\nconst { Sender } = __webpack_require__(/*! ./sender */ \"./src/protocol-v4/connection/sender.js\");\nconst { Receiver } = __webpack_require__(/*! ./receiver */ \"./src/protocol-v4/connection/receiver.js\");\nconst { MessageLedger } = __webpack_require__(/*! ./message-ledger */ \"./src/protocol-v4/connection/message-ledger.js\");\nconst { getGlobalIdentityCache } = __webpack_require__(/*! ./identity-cache */ \"./src/protocol-v4/connection/identity-cache.js\");\nconst { makeEBOIterator, setImmediateN, setImmediate } = __webpack_require__(/*! dcp/common/dcp-timers */ \"./src/common/dcp-timers.js\");\n\nconst { ConnectionMessage } = __webpack_require__(/*! ./connection-message */ \"./src/protocol-v4/connection/connection-message.js\");\nconst { ConnectionRequest } = __webpack_require__(/*! ./request */ \"./src/protocol-v4/connection/request.js\");\nconst { ConnectionResponse } = __webpack_require__(/*! ./response */ \"./src/protocol-v4/connection/response.js\");\nconst { ConnectionBatch } = __webpack_require__(/*! ./batch */ \"./src/protocol-v4/connection/batch.js\");\nconst { ConnectionAck } = __webpack_require__(/*! ./ack */ \"./src/protocol-v4/connection/ack.js\");\nconst { ErrorPayloadCtorFactory } = __webpack_require__(/*! ./error-payload */ \"./src/protocol-v4/connection/error-payload.js\");\nconst { role } = __webpack_require__(/*! ./connection-constants */ \"./src/protocol-v4/connection/connection-constants.js\");\n\nconst isDebugBuild = (__webpack_require__(/*! dcp/common/dcp-build */ \"./src/common/dcp-build.js\").build) === 'debug';\nlet nanoid;\nif (dcpEnv.platform === 'nodejs') {\n const { requireNative } = __webpack_require__(/*! dcp/dcp-client/webpack-native-bridge */ \"./src/dcp-client/webpack-native-bridge.js\");\n nanoid = requireNative('nanoid').nanoid;\n} else {\n nanoid = (__webpack_require__(/*! nanoid */ \"./node_modules/nanoid/index.browser.js\").nanoid);\n}\n\n\nlet globalConnectionId = 0;\nvar _msgId = 0;\n\nconst CONNECTION_STATES = [\n 'initial',\n 'connecting', /* initiator: establish first transport instance connection; target: listening */\n 'established',\n 'disconnected', /* connection is still valid, but underlying transport is no longer connected */\n 'close-wait', /* Target of close message is in this state until response is acknowledged */\n 'closing',\n 'closed',\n]\n\nclass Connection extends EventEmitter {\n static get VERSION() {\n return '5.1.0'; // Semver format\n }\n\n static get VERSION_COMPATIBILITY() {\n return '^5.0.0'; // Semver format, can be a range\n }\n\n /**\n * Connection form 2:\n * @constructor\n * @param {object} [target]\n * @param {Promise} idKsPromise A promise which resolves to the identity keystore described\n * in form 1\n * @param {object} [options]\n * @see form 1\n */\n /**\n * Connection form 1\n * Create a DCP Connection object. This object could represent either the initiator or \n * target end of a connection, until it is specialized by either invoke the connect()\n * or accept() methods. Note that send() invokes connect() internally if not in an established\n * state.\n * @constructor\n * @note Connection objects exist for the lifetime of a given DCP connection \n * (session), whether or not the underlying transport (eg internet protocol) is connected or not. Once \n * the DCP session has ended, this object has no purpose and is not reusable.\n * \n * @param {object} target Object (usually a dcpConfig fragment) describing the target.\n * This object may contain the following properties; 'location' is\n * mandatory:\n * - location: a URL or DcpURL that is valid from the Internet\n * - friendLocation: a DcpURL that is valid from an intranet; if\n * both location and friendLocation specified, the best one will\n * be chosen by examining IP addresses (assuming an IP bearer).\n * - identity: an object with an address property which is a promise\n * that resolves to an instance of wallet.Address which represents\n * to the target's identity; this overrides the initiator's \n * identity cache unless options.strict is truey.\n * \n * @param {Keystore} [idKeystore] The keystore used to sign messages; used for non-repudiation.\n * If not specified, a dynamically-generated keystore will be used.\n * \n * @param {object} [options] Extra connection options that aren't defined via dcpConfig.dcp.connectionOptions.\n * These options include:\n * - identityUnlockTimeout: Number of (floating-point) seconds to leave the identity \n * keystore unlocked between invocations of Connection.send\n *\n * @returns instance of Connection that is specific to a target URL but not a role\n */\n constructor(target, idKeystore, options = {})\n {\n super('Connection');\n this.id = ++globalConnectionId;\n this.debugLabel = `connection(g:${this.id}):`;\n\n /* polymorphism strategy: rewrite to (configFragment, idksPromise, options) */\n if (target instanceof DcpURL)\n target = { location: target };\n else if (DcpURL.isURL(target))\n target = { location: new DcpURL(target) };\n else if (target instanceof String || typeof target === 'string')\n target = { location: new DcpURL(target) };\n assert(typeof target === 'object', target.location);\n\n /* idKeystore is always resolved by the time a session is established. */\n if (!idKeystore)\n this.identityPromise = wallet.getId();\n else if (idKeystore instanceof Promise)\n this.identityPromise = idKeystore;\n else if (idKeystore instanceof wallet.Keystore)\n this.identityPromise = Promise.resolve(idKeystore); \n else if (idKeystore instanceof wallet.Address)\n this.identityPromise = Promise.resolve(new wallet.Keystore(idKeystore, '')); \n else\n throw new DCPError('Invalid argument provided for IdKeystore', 'DCPC-1018');\n\n this.identityPromise.then((keystore) => {\n /* This always happens by the time a role is assumed */\n delete this.identityPromise;\n this.identity = keystore;\n this.emit('ready');\n debugging('connection') && console.debug(this.debugLabel, 'identity is', this.identity.address);\n });\n\n this.target = target;\n this.url = this.target.location;\n \n // Init internal state / vars\n this.state = new Synchronizer(CONNECTION_STATES[0], CONNECTION_STATES);\n // DO NOT USE this.state.on('change', (s) => this.emit('readyStateChange', s) );\n\n this.dcpsid = null;\n this.peerAddress = null;\n this.transport = null;\n this.messageLedger = new MessageLedger(this);\n this.authorizedSender = null;\n this.preDisconnectState = null;\n \n this.Message = ConnectionMessage(this);\n this.Request = ConnectionRequest(this.Message);\n this.Response = ConnectionResponse(this.Message);\n this.Batch = ConnectionBatch(this.Message);\n this.Ack = ConnectionAck(this.Message);\n this.ErrorPayload = ErrorPayloadCtorFactory(this);\n this.connectTime = Date.now();\n\n this.receiver = new Receiver(this);\n this.sender = new Sender(this);\n \n debugging('connection') && console.debug(this.debugLabel, `new connection#${this.id}; ${this.url}`);\n\n /* Create a connection config as this.options which takes into\n * account system defaults and overrides for specific urls, origins, etc.\n */\n this.options = leafMerge(\n ({ /* hardcoded defaults insulate us from missing web config */\n 'connectTimeout': 90,\n 'lingerTimeout': 1800,\n 'allowBatch': true,\n 'maxMessagesPerBatch': 100,\n 'identityUnlockTimeout': 300,\n 'ttl': {\n 'min': 15,\n 'max': 600,\n 'default': 120\n },\n 'transports': [ 'socketio' ],\n 'messageTransmissionTolerance': 10,\n }),\n dcpConfig.dcp.connectionOptions.default,\n this.url && dcpConfig.dcp.connectionOptions[this.url.hostname],\n this.url && dcpConfig.dcp.connectionOptions[this.url.origin],\n options\n );\n\n /* draw out errors quickly in dev */\n if ((process.env.DCP_NETWORK_CONFIG_BUILD || dcpConfig.build) === 'debug')\n {\n this.options.maxMessagesPerBatch /= 10;\n \n /* short timeouts and debuggers don't get along well */\n if (dcpEnv.platform === 'nodejs' && !(requireNative('module')._cache.niim instanceof requireNative('module').Module))\n {\n this.options.connectTimeout /= 10;\n this.options.lingerTimeout /= 20;\n this.options.identityUnlockTimeout /= 10;\n }\n }\n\n assert(this.options.identityUnlockTimeout > 0);\n assert(this.options.connectTimeout > 0);\n assert(this.options.lingerTimeout > 0);\n assert(typeof this.options.ttl.min === 'number');\n assert(typeof this.options.ttl.max === 'number');\n assert(typeof this.options.ttl.default === 'number');\n \n this.backoffTimeIterator = makeEBOIterator(500, dcpConfig.build === 'debug' ? 5000 : 60000); /** XXXwg make this configurable */\n\n this.secureLocation = determineIfSecureLocation(this);\n this.loggableDest = '<generic>';\n }\n \n generateMessageId()\n {\n return `${this.id}-${_msgId++}-${Date.now()}-${nanoid()}`;\n }\n\n /**\n * Specialize an instance of Connection for either initiator or target behaviour. Once specialized,\n * the role cannot be changed. This happens based on methods invoked; connect() or accept() cause\n * the change.\n *\n * This specialization also implies that the connection is fully ready for use, including resolution\n * of the identity promise if necessary. This is perhaps not the best place to ensure that, but it\n * provides a reliable - and already async - waypoint to observe that event.\n */\n async a$assumeRole(myRole)\n {\n assert(myRole === role.initiator || myRole === role.target);\n\n if (this.role === myRole)\n return;\n this.role = myRole;\n \n if (this.role === role.target)\n {\n this.debugLabel = `connection(t:${this.id}):`;\n this.sender.debugLabel = `sender(t#${this.id}):`;\n this.messageLedger.debugLabel = `message-ledger(t#${this.id}):`;\n this.loggableDest = '<target>';\n this.hasNtp = true;\n }\n else\n {\n this.debugLabel = `connection(i:${this.id}):`;\n this.sender.debugLabel = `sender(i#${this.id}):`;\n this.messageLedger.debugLabel = `message-ledger(i#${this.id}):`;\n this.loggableDest = this.url.href;\n this.hasNtp = false;\n }\n\n debugging('connection') && console.debug(this.debugLabel, `connection #${this.id} is ${this.role} for ${this.url}`);\n if (!this.identity)\n {\n assert(this.identityPromise);\n debugging('connection') && console.debug(this.debugLabel, `waiting for identity resolution`);\n await this.identityPromise;\n }\n }\n\n /**\n * API to establish a DCP connection. Implied by send().\n *\n * When invoked by the initiator, this method establishes the connection by connecting\n * to the target url provided to the constructor.\n */\n async connect() // eslint-disable-line require-await\n {\n if (this.role == role.target)\n return;\n \n if (!this.role)\n await this.a$assumeRole(role.initiator);\n \n if (this.state.is('initial'))\n {\n if (!this.connectPromise)\n this.connectPromise = Promise.race([this.connectTimer(), this.a$_connect()]).then(() => { clearTimeout(this.connectTimeoutPromise); delete this.connectPromise });\n return this.connectPromise;\n }\n\n if (this.state.is('disconnected'))\n {\n if (!this.connectPromise)\n this.connectPromise = this.a$_reconnect().then(() => delete this.connectPromise);\n return this.connectPromise;\n }\n \n if (this.state.is('connecting'))\n {\n assert(this.connectPromise);\n return this.connectPromise;\n }\n\n if (this.state.is('established'))\n return;\n \n if (this.state.in(['closed', 'close-wait', 'closing']))\n throw new DCPError('Connection already closed', 'DCPC-1015');\n\n throw new Error('impossible');\n }\n\n /**\n * Performs a reconnection for connections which are in the disconnected state, and\n * tries to send any in-flight or enqueued messages as soon as that happens.\n */\n async a$_reconnect()\n {\n var transport;\n\n this.state.testAndSet('disconnected', 'connecting');\n try\n {\n do\n {\n transport = await this.a$connectToTargetTransport();\n } while (!transport && (this.transport && !this.transport.ready()) && !this.state.in(['closed', 'close-wait', 'closing']));\n\n debugging('connection') && console.debug(this.debugLabel, `reconnected via transport ${transport.socket.id}`);\n \n this.useNewTransport(transport);\n }\n catch (error)\n {\n if (error.code !== 'DCPC-1016' && error.code !== 'DCPC-1015')\n {\n /* Unreached unless there are bugs. */\n throw error;\n } \n this.close(error, true);\n }\n }\n\n connectTimer()\n {\n return new Promise((_resolve, reject) =>\n {\n var timeoutMs = (this.options.connectTimeout2 || 31) * 1000;\n \n this.connectTimeoutPromise = setTimeout(() =>\n { \n reject(new Error(`Failed to establish connection to ${this.url} within ${(timeoutMs / 1000).toFixed(1)} seconds`));\n }, timeoutMs);\n if (typeof this.connectTimeoutPromise.unref === 'function')\n this.connectTimeoutPromise.unref();\n });\n }\n \n async a$_connect()\n { \n var presharedPeerAddress, establishResults;\n var targetIdentity = await this.target.identity;\n var transport;\n \n assert(this.role === role.initiator);\n\n this.state.set('initial', 'connecting');\n do\n {\n transport = await this.a$connectToTargetTransport().catch((error) =>\n {\n debugging('connection') && console.debug(`${this.debugLabel} error connecting to target on transport layer:`, error);\n return { ready: () => {return false} };\n });\n } while(!transport.ready());\n this.adopt(transport);\n\n establishResults = await this.sender.establish().catch(error => {\n debugging('connection') && console.debug(this.debugLabel, `Could not establish DCP session ${this.transport ? 'over' + this.transport.name : '. Transport establishment was not complete'}:`, error);\n this.close(error, true);\n throw error;\n });\n const peerAddress = new wallet.Address(establishResults.peerAddress);\n const dcpsid = establishResults.dcpsid;\n debugging('connection') && console.debug(this.debugLabel, 'dcpsid is', dcpsid);\n \n if (!this.options.strict && targetIdentity && determineIfSecureConfig())\n {\n if ( false\n || typeof targetIdentity !== 'object'\n || typeof targetIdentity.address !== 'object'\n || !(targetIdentity.address instanceof wallet.Address))\n targetIdentity = { address: new wallet.Address(targetIdentity) }; /* map strings and Addresses to ks ducks */\n\n presharedPeerAddress = targetIdentity.address;\n debugging('connection') && console.debug(this.debugLabel, 'Using preshared peer address', presharedPeerAddress);\n }\n this.ensureIdentity(peerAddress, presharedPeerAddress);\n\n /* At this point, new session is valid & security checks out - make Connection instance usable */\n this.peerAddress = peerAddress;\n if (this.dcpsid)\n throw new DCPError(`Reached impossible state in connection.js; dcpsid already specified ${this.dcpsid} (${this.url})`, 'DCPC-1012');\n\n this.state.set('connecting', 'established'); /* established => dcpsid has been set */\n this.emit('session', (this.dcpsid = dcpsid));\n this.emit('connect', this.url);\n this.sender.notifyTransportReady();\n return Promise.resolve();\n }\n\n /**\n * unreference any objects entrained by this connection so that it does not prevent\n * the node program from exiting naturally.\n */\n unref()\n {\n if (this.connectAbortTimer && this.connectAbortTimer.unref && dcpEnv.platform === 'nodejs')\n this.connectAbortTimer.unref();\n }\n\n /**\n * Method is invoked when the transport disconnects. Transport instance is responsible for its own\n * finalization; Connection instance is responsible for finding a new transport, resuming the\n * connection, and retransmitting any in-flight message.\n *\n * @param {object} transport the transport instance that triggered this handler. In some cases, it\n * is possible that this event is not serviced until after the connection\n * has already acquired a new transport instance, e.g. in a Target where\n * the initiator switched networks. This implies that it is possible for\n * more 'connect' events to be emitted than 'disconnect' events.\n */\n transportDisconnectHandler(transport)\n {\n try\n { \n if (this.state.in(['disconnected', 'closing', 'close-wait', 'closed'])) /* transports may fire this more than once */\n return;\n\n if (transport !== this.transport) /* event no longer relevant */\n return;\n\n if (this.transport)\n {\n transport.close();\n delete this.transport;\n }\n \n if (this.state.is('established'))\n {\n this.state.set('established', 'disconnected');\n this.emit('disconnect', this.url); /* UI hint: \"internet unavailable\" */\n debugging('connection') && console.debug(this.debugLabel, `Transport disconnected from ${this.url}; ${this.sender.inFlight ? 'have' : 'no'} in-flight message`);\n \n if (!this.dcpsid) /* hopefully impossible? */\n {\n debugging('connection') && console.debug(this.debugLabel, 'Not reconnecting - no session');\n return;\n }\n }\n \n if (this.role === role.target)\n {\n /* targets generally can't reconnect due to NAT */\n debugging('connection') && console.debug(this.debugLabel, `Waiting for initiator to reconnect for ${this.dcpsid}`);\n return;\n }\n \n if (this.dcpsid && !this.sender.inFlight && this.options.onDemand)\n {\n debugging('connection') && console.debug(this.debugLabel, `Not reconnecting ${this.dcpsid} until next message`);\n return;\n }\n \n if (this.state.is('connecting') && (!this.dcpsid || !this.peerAddress))\n {\n debugging('connection') && console.debug(this.debugLabel, `Disconnected while connecting, establishing transport and re-sending connect request.`);\n this.a$_reconnect();\n return;\n }\n\n /* At this point, we initiate a (re)connect attempt because either\n * - we haven't connected yet,\n * - we have something to send, or\n * - we are not an on-demand connection\n */\n if (!this.state.is('connecting'))\n this.connect();\n }\n catch(error)\n {\n debugging('connection') && console.debug(this.debugLabel, 'unexpected error handling disconnect:', error);\n this.close(error, true);\n \n if (error.code !== 'DCPC-1016' && error.code !== 'DCPC-1015')\n {\n /* Unreached unless there are bugs. */\n throw error;\n }\n }\n }\n \n /**\n * Initiators only\n *\n * Connect to a target at the transport level.\n * - Rejects when we give up on all transports.\n * - Resolves with a transport instance when we connect to one.\n *\n * The connection attempt will keep a node program \"alive\" while it is happening.\n * The `autoUnref` connectionOption and unref() methods offer ways to make this not\n * happen.\n */\n async a$connectToTargetTransport()\n {\n const that = this;\n const availableTransports = [].concat(this.options.transports);\n var quitMsg = false; /* not falsey => reject asap, value is error message */\n var quitCode = undefined;\n var boSleepIntr; /* if not falsey, a function that interrupts the backoff sleep */\n var transportConnectIntr; /* if not falsey, a function that interrupts the current connection attempt */\n\n // Already trying to connect to target, don't try multiple times until we've aborted one attempt\n if (this.connectAbortTimer)\n return;\n \n /* This timer has the lifetime of the entire connection attempt. When we time out,\n * we set the quitMsg to get the retry loop to quit, then we interrupt the timer so\n * that we don't have to wait for the current backoff to expire before we notice, and\n * we expire the current attempt to connect right away as well.\n */\n this.connectAbortTimer = setTimeout(() => {\n quitMsg = 'connection timeout';\n if (boSleepIntr) boSleepIntr();\n if (transportConnectIntr) transportConnectIntr();\n }, this.options.connectTimeout * 1000);\n\n if (this.options.autoUnref)\n this.unref();\n\n /* cleanup code called on return/throw */\n function cleanup_ctt()\n {\n clearTimeout(that.connectAbortTimer);\n delete that.connectAbortTimer;\n }\n\n /* Connect to target with a specific transport. */\n /* Resolves with { bool success, obj transport } or rejects with { error } if the transport cannot connect*/\n function a$connectWithTransport(transportName)\n { \n transportConnectIntr = false;\n\n return new Promise((connectWithTransport_resolve, connectWithTransport_reject) => { \n const TransportClass = Transport.require(transportName);\n const transport = new TransportClass(that.target, that.options[transportName]);\n var ret = { transport };\n\n function cleanup_cwt()\n {\n for (let eventName of transport.eventNames())\n for (let listener of transport.listeners(eventName))\n transport.off(eventName, listener);\n }\n \n /* In the case where we have a race condition in the transport implementation, arrange things\n * so that we resolve with whatever fired last if we have a double-fire on the same pass of \n * the event loop.\n */\n transport.on('connect', () => { cleanup_cwt(); ret.success=true; connectWithTransport_resolve(ret) });\n transport.on('error', (error) => { cleanup_cwt(); connectWithTransport_reject(error) });\n transport.on('connect-failed', (error) => {\n cleanup_cwt();\n ret.success = false;\n ret.error = error;\n debugging() && console.debug(that.debugLabel, `Error connecting to ${that.url};`, error);\n connectWithTransport_resolve(ret);\n });\n \n /* let the connectAbortTimer interrupt this connect attempt */\n transportConnectIntr = () => { transport.close() };\n });\n }\n \n if (availableTransports.length === 0)\n {\n cleanup_ctt();\n return Promise.reject(new DCPError('no transports defined', 'DCPC-1014'));\n }\n \n /* Loop while trying each available transport in turn. Sleep with exponential backoff between runs */\n while (!quitMsg)\n {\n for (let transportName of availableTransports)\n {\n try\n {\n const { success, error, transport } = await a$connectWithTransport(transportName);\n\n /* Have connected to the remote at the transport level - OUT */\n if (success === true)\n {\n transportConnectIntr = false;\n cleanup_ctt();\n \n return transport;\n }\n\n /* Fast-fail: certain - but few - HTTP status codes let us know that this (or any) transport\n * will never work, so don't try those again.\n */\n if (error && error.httpStatus)\n {\n switch(error.httpStatus)\n {\n case 301: case 302: case 303: case 307: case 308:\n debugging('connection') && console.debug(this.debugLabel, `HTTP status ${error.httpStatus}; won't try again with ${transportName}`);\n availableTransports.splice(availableTransports.indexOf(transportName), 1);\n break;\n case 400: case 403: case 404:\n debugging('connection') && console.debug(this.debugLabel, `HTTP status ${error.httpStatus}; won't try again.`);\n quitMsg = error.message;\n quitCode = 'HTTP_' + error.httpStatus || 0;\n break;\n default:\n debugging('connection') && console.debug(this.debugLabel, `HTTP status ${error.httpStatus}; will try again with ${transportName}`);\n break;\n }\n }\n }\n catch (impossibleError)\n {\n /* transport connection attempts should never throw. */\n debugging('connection') && console.debug(this.debugLabel, `Error connecting to ${this.url} with ${transportName}; won't try again:`, impossibleError);\n availableTransports.splice(availableTransports.indexOf(transportName), 1);\n }\n }\n \n if (availableTransports.length === 0)\n {\n quitMsg = 'all transports exhausted';\n break;\n }\n \n /* Go to (interruptible) sleep for a while before trying again */\n const backoffTimeMs = this.backoffTimeIterator.next().value;\n debugging('connection') && console.debug(this.debugLabel, 'trying again in', Number(backoffTimeMs / 1000).toFixed(2), 'seconds');\n const boSleepPromise = a$sleepMs(backoffTimeMs);\n boSleepIntr = boSleepPromise.intr;\n await boSleepPromise;\n boSleepIntr = false;\n }\n\n /* The only way we get here is for us to discover that the connection is unconnectable - eg \n * reject timer has expired or similar.\n */\n cleanup_ctt();\n throw new DCPError(quitMsg, 'DCPC-1016', quitCode);\n }\n\n /**\n * Method which must be invoked whenever a new transport needs to be assigned to the connection. \n * If we have previously adopted a transport, we close it first, which will prevent \n * [by transport definition] any message handlers from firing, even if the old transport instance\n * has buffered traffic.\n *\n * @param {object} transport transport instance\n */\n adopt(transport)\n {\n if (this.transport)\n this.transport.close();\n\n transport.on('message', (m) => this.handleMessage(m));\n transport.on('end', () => this.transportDisconnectHandler(transport));\n transport.on('close', () => this.transportDisconnectHandler(transport));\n \n this.transport = transport;\n }\n \n \n /**\n * Method that gets invoked when there is a new transport available for adoption.\n * This will adjust the state of the connection, adopt the transport then tell\n * the sender to pump the message queue.\n * @param {object} transport transport instance \n */\n useNewTransport(transport)\n {\n if (this.state.in(['closing', 'close-wait', 'closed']))\n {\n debugging('connection') && console.debug(`${this.debugLabel} got a new transport during closing. closing the new transport and not completing transport adoption.`)\n transport.close();\n return;\n }\n \n var preDisconnectState = (!this.dcpsid || !this.peerAddress) ? 'connecting' : 'established';\n if (this.state.isNot(preDisconnectState))\n this.state.set(['connecting', 'disconnected'], preDisconnectState);\n this.adopt(transport);\n this.emit('connect', this.url); // UI hint: \"internet available\" \n this.sender.notifyTransportReady();\n }\n \n /**\n * Method that must be invoked by the target to \"accept\" a new DCP Connection request\n * at the transport layer.\n * @param {object} transport \n */\n async accept(transport)\n {\n assert(!this.role);\n await this.a$assumeRole(role.target);\n this.state.set('initial', 'connecting');\n this.adopt(transport);\n }\n \n /**\n * This method is invoked by the target when it has handled the initial connect request from\n * the initiator, which contains the peerAddress and the first half of the dcpsid (the second half is\n * populated by receiver.handleFirstRequest before being passed here). It transitions the connection \n * into an established state at the protocol level.\n * Note - this is really not the right design for this, but it is invoked from handleFirstRequest\n * in ./receiver.js\n *\n * @param {string} dcpsid dcpsid\n * @param {wallet.Address} peerAddress Address of peer\n */\n establishTarget(dcpsid, peerAddress) {\n assert(this.role === role.target);\n \n this.connectResponseId = Symbol('dummy'); // un-register ConnectResponse\n this.peerAddress = peerAddress;\n if (this.dcpsid)\n throw new DCPError(`Reached impossible state in connection.js; dcpsid already specified ${this.dcpsid}!=${dcpsid} (${this.url})`, 'DCPC-1005');\n this.emit('session', (this.dcpsid = dcpsid));\n debugging() && console.debug(this.debugLabel, 'dcpsid is', dcpsid);\n\n this.loggableDest = this.role === role.initiator ? this.url : peerAddress;\n this.state.set('connecting', 'established'); /* established => dcpsid has been set */\n\n debugging('connection') && console.debug(this.debugLabel, `Established session ${this.dcpsid} with ${this.peerAddress} for ${this.url}`);\n }\n\n /**\n * Check to see if the peer address conflicts with what we have in the global identity cache;\n * it does, throw an exception.\n */\n ensureIdentity (peerAddress, presharedPeerAddress)\n {\n let idc = getGlobalIdentityCache();\n let noConflict = idc.learnIdentity(this.url, peerAddress, presharedPeerAddress);\n\n if (!noConflict)\n throw new DCPError(`**** Security Error: Identity address ${peerAddress} does not match the saved key for ${this.url}`, 'DCPC-EADDRCHANGE');\n }\n \n \n /**\n * This method uses the first request (if we're target) or ack to the first request \n * (if we're initiator) to memoize the address of the peer authorized to send to us. \n * The first message must only be a request (since it comes from sender.specialFirstSend)\n * that has a connect operation, or an acknowledgement of the first request. \n * All future messages' owners will be validated against this authorized sender.\n * @param {Object} message \n */\n setAuthorizedSender(message)\n {\n if (message.body.type !== 'request' && message.body.type !== 'ack')\n throw new DCPError('First protocol message was not a request or ack', 'DCPC-1017');\n \n if (message.body.type === 'request' && message.body.payload.operation !== 'connect')\n throw new DCPError('First protocol message did not contain the correct payload', 'DCPC-1017');\n \n if (message.body.type === 'ack' && this.sender.inFlight.message.payload.operation !== 'connect')\n throw new DCPError('First protocol acknowledgement was not for connect request', 'DCPC-1017');\n \n this.authorizedSender = message.owner;\n }\n \n /**\n * Emits an error event with the relevant error and closes the connection immediately.\n * @param {string} errorMessage \n * @param {string} errorCode \n */\n \n handleValidationError(errorMessage, errorCode)\n {\n var messageError;\n \n debugging('connection') && console.debug(this.debugLabel, 'Message failed validation -', errorMessage);\n this.emit('error', (messageError = new DCPError(`message failed validation: ${errorMessage}`, errorCode)))\n this.close(messageError, true);\n }\n\n handleTooManyMessageTransmissions()\n {\n var messageError;\n \n debugging('connection') && console.debug(`Attempts to send message exceeded tolerance of ${this.options.messageTransmissionTolerance} times.`);\n this.emit('error', (messageError = new DCPError(`Message reached tolerance of ${this.options.messageTransmissionTolerance} send attempts.`)));\n this.close(messageError, true);\n }\n\n /**\n * This method validates the message owner, signature and body before passing it onto\n * either the receiver (for a request, response or batch) or the messageLedger (for an ack).\n * If it's a request, response or batch, this method also provokes the connection to \n * send an acknowledgement (ack) to the peer to let them know we got their message.\n * XXXwg this code needs an audit re error handling: what message error should we be emitting?\n * why do we keep working after we find an error?\n * XXXsc did some auditing. we happy now?\n * @param {string} JSON-encoded unvalidated message object\n */\n async handleMessage (messageJSON) {\n var validation;\n var message;\n\n if (this.state.is('closed')) {\n debugging('connection') && console.debug(this.debugLabel, 'handleMessage was called on a closed connection.');\n return;\n }\n\n try\n {\n message = typeof messageJSON === 'object' ? messageJSON : JSON.parse(messageJSON);\n debugging('wire') && console.debug(this.debugLabel, `handleMessage: ${String(message && message.body && message.body.type).padEnd(10, ' ')} <- ${this.loggableDest}`);\n }\n catch(error)\n {\n console.error('connection::handleMessage received unparseable message from peer:', error);\n this.emit('error', error);\n return;\n }\n \n /**\n * We always ack a duplicate transmission.\n * This must happen before validation since during startup we may lack a\n * nonce or dcpsid (depending on whether initiator or target + race).\n */\n if (this.isDuplicateTransmission(message)) {\n debugging('connection') && console.debug(this.debugLabel, 'duplicate message:', message.body);\n debugging('wire') && console.debug(this.debugLabel, `dup message ack: ${String(message.body.type).padEnd(10, ' ')} -> ${this.loggableDest}`);\n\n this.sendAck(this.lastAckSigned) \n return;\n }\n\n debugging('connection') && console.debug(this.debugLabel, `received message ${message.body.type} ${message.body.id}; nonce=`, message.body.nonce);\n \n validation = this.validateMessageDCPSID(message);\n if (validation.success !== true)\n {\n this.handleValidationError(validation.errorMessage, 'ENODCPSID');\n return;\n }\n\n validation = this.validateMessageOwner(message)\n if (validation.success !== true)\n {\n this.handleValidationError(validation.errorMessage, 'EINVAL');\n return;\n }\n \n validation = this.validateMessageSignature(message);\n if (validation.success !== true)\n {\n this.handleValidationError(validation.errorMessage, 'EINVAL');\n return;\n }\n\n validation = this.validateMessageBody(message);\n if (validation.success !== true)\n {\n this.handleValidationError(validation.errorMessage, validation.errorCode || 'EINVAL'); /* messages of type 'unhandled-message' may contain more information about the failure */\n return;\n }\n \n if (message.body.type === \"ack\") {\n const ack = new this.Ack(message.body);\n this.messageLedger.handleAck(ack);\n return;\n } else if (message.body.type !== 'unhandled-message') {\n this.lastMessage = message;\n await this.ackMessage(message);\n }\n \n this.receiver.handleMessage(message);\n }\n\n \n /**\n * This method takes either a Request, Response or Batch, creates an ack for it\n * and sends it to the peer. This ack contains the nonce we expect on the next\n * message from peer.\n * @param {Connection.Message} message \n */\n async ackMessage(message) {\n debugging('connection') && console.debug(this.debugLabel, 'acking message of type: ', message.body.type);\n const ack = new this.Ack(message);\n const signedMessage = await ack.sign(this.identity);\n\n debugging('wire') && console.debug(this.debugLabel, `ackMessage: ${String(message.body.type).padEnd(10, ' ')} -> ${this.loggableDest}`);\n\n this.sendAck(signedMessage);\n this.lastAck = ack;\n this.lastAckSigned = signedMessage;\n }\n\n /**\n * Checks if the batch we just received has the same nonce\n * as the most-recently received batch.\n * @param {object} messageJSON\n */\n isDuplicateTransmission(messageJSON) {\n return this.lastMessage && this.lastMessage.body.nonce && this.lastMessage.body.nonce === messageJSON.body.nonce;\n }\n\n /**\n * Validate that the message came from the appropriate sender.\n * @param {Object} message the message to validate\n * @returns {Object} returns an object `ret` with either ret.success = true, \n * or ret.success = false accompanied by another property ret.errorMessage\n */\n validateMessageOwner(message)\n {\n if (!this.authorizedSender)\n {\n /* Capture the initial identity of the remote end during the connect operation */\n this.setAuthorizedSender(message);\n return { success: true }\n }\n else if (message.owner !== this.authorizedSender)\n {\n return { success: false, errorMessage: \"message came from unauthorized sender\" }\n }\n return { success: true }\n }\n \n /**\n * Validate that the signature was generated from this message body\n * @param {Object} message\n * @returns {Object} with properties 'success' and 'errorMessage'. When the message is valid on its \n * face, the success property is true, otherwise it is is false. When it is false,\n * the errorMessage property will be a string explaining why.\n */\n validateMessageSignature(message)\n {\n if (!message.signature) {\n debugging('connection') && console.warn(this.debugLabel, \"Message does not have signature, aborting connection\");\n return { success: false, errorMessage: \"message is missing signature\" };\n }\n \n const owner = new wallet.Address(message.owner);\n const signatureValid = owner.verifySignature(message.body, message.signature);\n\n if (!signatureValid)\n {\n debugging('connection') && console.warn(this.debugLabel, \"Message has an invalid signature, aborting connection\");\n return { success: false, errorMessage: \"invalid message signature\" };\n }\n\n return { success: true };\n }\n \n validateMessageDCPSID(message)\n {\n if (this.dcpsid !== null && message.dcpsid)\n {\n if (message.dcpsid !== this.dcpsid)\n {\n debugging('connection') && console.warn(this.debugLabel, 'Message has an invalid dcpsid, aborting connection');\n return { success: false, errorMessage: 'message has an invalid dcpsid' };\n }\n }\n \n return { success: true };\n }\n /**\n * This method is used to perform validation on all types of messages.\n * It validates the DCPSID, nonce, and the peerAddress.\n * @param {Object} message\n * @returns {Object} with properties 'success' and 'errorMessage'. When the message is valid on its \n * face, the success property is true, otherwise it is is false. When it is false,\n * the errorMessage property will be a string explaining why.\n *\n */\n validateMessageBody(message)\n {\n try\n {\n if (message.body.type === 'unhandled-message')\n {\n /* This special message type may not have a dcpsid, peerAddress, etc., so it might not\n * validate. It is also not a \"real\" message and only used to report ConnectionManager routing \n * errors, so we just report here, drop it, and close the connection.\n *\n * Note also that this is probably the wrong way to handle this case - restarting daemons - but\n * that is a problem for another day. /wg nov 2021\n */\n debugging('connection') && console.warn(this.debugLabel, \"Target Error - target could not process message.\", JSON.stringify(message.body),\n \"Aborting connection.\");\n return { success: false, errorMessage: `target could not process message (${message.body.payload && message.body.payload.message || 'unknown error'})`, errorCode: message.body.payload && message.body.payload.code}\n }\n if (this.peerAddress && !this.peerAddress.eq(message.owner))\n {\n debugging('connection') && console.warn(this.debugLabel,\n \"Received message's owner address does not match peer address, aborting connection\\n\",\n \"(owner addr)\", message.owner, '\\n',\n \"(peer addr)\", this.peerAddress);\n return { success: false, errorMessage: \"received message owner does not match peer address\" };\n }\n\n if (this.state.in(['established', 'closing', 'close-wait']) && message.body.type !== 'unhandled-message')\n {\n const body = message.body;\n\n assert(this.peerAddress); /* should be set in connect */\n /**\n * Security note:\n * We don't require the dcpsid to match on an ack because the connect response\n * ack doesn't have a dcpsid until after it is processed. Also ack's are protected\n * by ack tokens and signatures, so this doesn't leave a hole, just an inconsistency.\n */\n if (body.type !== 'ack' && body.dcpsid !== this.dcpsid)\n {\n debugging('connection') && console.warn(this.debugLabel,\n \"Received message's DCPSID does not match, aborting connection\\n\",\n \"Message owner:\", message.owner, '\\n',\n \"(ours)\", this.dcpsid, (Date.now() - this.connectTime)/1000, \"seconds after connecting - state:\", this.state._, \"\\n\", \n \"(theirs)\", body.dcpsid);\n if(body.dcpsid.substring(0, body.dcpsid.length/2) !== this.dcpsid.substring(0, this.dcpsid.length/2)){\n debugging('connection') && console.warn(this.debugLabel, \" Left half of both DCPSID is different\");\n }\n if(body.dcpsid.substring(body.dcpsid.length/2 + 1, body.dcpsid.length) !== this.dcpsid.substring(this.dcpsid.length/2 + 1, body.dcpsid.length)){\n debugging('connection') && console.warn(this.debugLabel, \" Right half of both DCPSID is different\");\n }\n return { success: false, errorMessage: \"DCPSID do not match\" };\n }\n /* can get close in middle of connecting, which will have no nonce.*/\n if (body.type !== 'ack' && this.lastAck.nonce !== body.nonce && (body.payload && body.payload.operation !== 'close'))\n {\n /* When Target sends back ConnectionLessErrorResponse, it uses the nonce of the message that caused an error. */\n if (this.sender.inFlight && this.sender.inFlight.message.nonce === body.nonce)\n {\n debugging('connection') && console.debug(`${this.debugLabel} Received messages nonce matches nonce of our current inFlight message.`,\n \"There was a problem sending this message. Aborting connection. Reason:\\n\", body.payload);\n return { success: false, errorMessage: \"current inflight message returned an error\" }\n }\n debugging('connection') && console.warn(this.debugLabel,\"Received message's nonce does not match expected nonce, aborting connection\\n\");\n debugging('connection') && console.debug(this.debugLabel, this.lastAck.nonce, body.nonce);\n return { success: false, errorMessage: \"received message's nonce does not match expected nonce\" };\n }\n if (body.type === 'request') \n {\n if (body.payload.validity.time === undefined)\n return { success: false, errorMessage: 'received message does not have a valid time in its payload' };\n }\n }\n\n return { success: true };\n }\n catch(error)\n {\n console.error('message validator failure:', error);\n return { success: false, errorMessage: 'validator exception ' + error.message };\n }\n\n return { success: false, errorMessage: 'impossible code reached' }; // eslint-disable-line no-unreachable\n }\n\n /**\n * Targets Only.\n * The receiver creates a special connect response and the connection\n * needs to know about it to get ready for the ack. See `isWaitingForAck`.\n * @param {Message} message message we are sending out and waiting to\n * ack'd, probably a batch containing the response.\n */\n registerConnectResponse(message) {\n this.connectResponseId = message.id;\n }\n\n /**\n * Targets only\n * During the connection process a target sends a connect\n * response to an initiator and the initiator will ack it. Since transports\n * are not tightly coupled, we have no authoritative way to route the ack back\n * to the right connection. So a connection briefly registers the ack it\n * is looking for in this case. It will formally validate the ack after routing.\n * @param {string} messageId id of the message this ack is acknowledging.\n */\n isWaitingForAck(messageId) {\n return messageId === this.connectResponseId;\n }\n\n /**\n * Put connection into close-wait state so that a call to `close`\n * in this state will *not* trigger sending a `close` message to the peer.\n * Then call close.\n *\n * @note: This function is called when the remote end of the transport sends\n * a close command, from receiver::handleOperation. This impllies that\n * that we must be in established or later state.\n */\n closeWait (errorCode = null)\n {\n var preCloseState, reason;\n \n debugging('connection') && console.debug(this.debugLabel, `responding to close. state=closeWait dcpsid=${this.dcpsid}`);\n\n if (this.state.is('closed'))\n {\n debugging('connection') && console.debug(this.debugLabel, `remote asked us to close a closed connection; dcpsid=${this.dcpsid}`);\n return;\n }\n\n // continue with close in either case\n reason = `Received close from peer with Error Code ${errorCode}`;\n if (this.role === role.target)\n reason += ` (${this.url})`;\n else\n reason += ` (${this.debugLabel}${this.peerAddress.address})`;\n\n reason = new DCPError(reason, errorCode || 'DCPC-1011');\n\n // If we're already closing, wait for it to complete then resolve\n // WARNING: any place we transition to closing or close-wait, we MUST guarantedd\n // that 'end' will be emitted, or this code will hang forever!\n if (this.state.in(['close-wait', 'closing'])) {\n return new Promise((resolve) => {\n this.once('end', resolve) /* eventually fired by doClose elsewhere */\n });\n }\n\n /* XXXwg - this should only be established->close-wait. Why more? */\n this.state.set(['disconnected', 'connecting', 'established'], 'close-wait');\n \n /* Set preCloseState to close-wait so doClose doesn't send a close message back */\n preCloseState = this.state.valueOf();\n return this.doClose(preCloseState, reason, true);\n }\n\n /**\n * This method will begin closing the protocol connection. It transitions\n * the protocol into the correct state, and then begins the work of closing.\n * \n * @param {string|Error} [reason] Either an Error or a message to use in the Error that will reject pending sends.\n * @param {boolean} [immediate] When true, the connection will not deliver any pending messages and instead\n * immediately send the peer a 'close' request. \n *\n * @return a Promise which resolves when the connection has been confirmed closed and the end event has been fired.\n */\n close (reason='requested', immediate=false)\n {\n if (this.state.is('initial'))\n {\n debugging('connection') && console.debug(this.debugLabel, 'not yet established; closing directly.');\n this.state.set('initial', 'closed');\n this.emit('close'); /* Don't emit dcpsid */\n }\n if (this.state.is('closed')) return Promise.resolve();\n\n const preCloseState = this.state.valueOf();\n debugging('connection') && \n console.debug(this.debugLabel, \n `close; dcpsid=${this.dcpsid} state=${preCloseState} immediate=${immediate} reason:`, reason);\n\n // If we're already closing, wait for it to complete then resolve\n if (this.state.in(['close-wait', 'closing'])) {\n debugging('connection') && console.debug(this.debugLabel, 'already closing; awaiting `end` event...');\n return new Promise((resolve) => {\n this.once('end', resolve)\n });\n }\n\n this.state.set(['connecting', 'established', 'disconnected'], 'closing');\n\n // Perform actual work of closing\n return this.doClose(preCloseState, reason, immediate);\n }\n\n /**\n * Sends close message to peer after sending all pending messages.\n * Note that close messages are sent without the expectation of a response.\n * @param {DCPError|string} reason reason for closing\n */\n async sendCloseGracefully(reason) \n {\n debugging('connection') && console.debug(`${this.debugLabel} gracefully sending close message to peer with reason ${reason}`)\n let errorCode = reason instanceof DCPError ? reason.code : 'DCPC-1011';\n \n /* This only resolves when close is next message in queue */\n const closeMessage = await this.prepare('close', { errorCode: errorCode });\n debugging('connection') && console.debug(`1249: close message prepared; sending...`);\n this.sendPreparedMessage(closeMessage);\n this.messageLedger.fulfillMessagePromise(closeMessage.message.id, {});\n debugging('connection') && console.debug(`1251: close message prepared; sending...`);\n }\n \n /**\n * Sends close message to peer immediately. Pending messages will not be sent.\n * Note that close messages are sent without expectation of response.\n * @param {DCPError|string} reason reason for closing\n */\n async sendCloseImmediately(reason)\n {\n debugging('connection') && console.debug(`${this.debugLabel} immediately sending close message to peer with reason ${reason}`);\n let errorCode = reason instanceof DCPError ? reason.code : 'DCPC-1011';\n \n /* Last param being `true` means that prepareMessage will return unsigned message. Does not queue message. */\n const closeMessage = await this.prepare('close', { errorCode: errorCode }, true);\n \n if (this.sender.inFlight)\n closeMessage.nonce = this.sender.inFlight.message.nonce;\n else\n closeMessage.nonce = this.sender.nonce;\n \n let signedCloseMessage = await closeMessage.sign();\n \n /* Overwrite the in-flight message because we don't care to deliver pending messages */\n this.sender.inFlight = { message: closeMessage, signedMessage: signedCloseMessage };\n this.sender.sendInFlightMessage();\n }\n \n /**\n * This method performs the core close functionality. It appropriately sends the close message\n * to the peer, fails any pending transmissions, shuts down our sender and underlying transport\n * and puts us into the 'closed' state, indicating this connection object is now useless.\n * When called from closeWait, it does not send a close message.\n * @param {string} preCloseState the state that the connection was in at the start of the\n * invocation of close() or closeWait()\n *\n * @note: this function is not reentrant due to closeGracefully\n */\n async doClose(preCloseState, reason, immediate) {\n const dcpsid = this.dcpsid;\n var rejectErr;\n\n try\n {\n // Emit the close event the moment we know we are going to close, \n // so we can catch the close event and reopen the connection\n //\n // This implies that no API functions which call doClose may await between\n // their invocation and their call to doClose!\n this.emit('close', dcpsid /* should be undefined in initial state */);\n\n assert(this.state.in(['closing', 'close-wait']));\n if (preCloseState === 'established' && this.transport) {\n try {\n if (immediate) {\n await this.sendCloseImmediately(reason);\n } else {\n await this.sendCloseGracefully(reason);\n }\n } catch(e) {\n debugging() && console.warn(this.debugLabel, `Warning: could not send close message to peer. connectionid=${this.id}, dcpsid=,${this.dcpsid}, url=${this.url ? this.url.href : 'unknown url'} - (${e.message})`);\n }\n }\n\n // can delete these now that we've sent the close message\n this.dcpsid = null;\n this.peerAddress = null;\n\n if (reason instanceof DCPError)\n rejectErr = reason;\n else\n {\n let errorMessage = reason instanceof Error ? reason : `Connection to ${this.loggableDest} closed (${reason})`;\n rejectErr = new DCPError(errorMessage, 'DCPC-1013');\n }\n \n // Reject any pending transmissions in the message ledger\n this.messageLedger.failAllTransmissions(rejectErr);\n \n if (this.transport)\n {\n try { this.sender.shutdown(); }\n catch(e) { debugging() && console.warn(this.debugLabel, `Warning: could not shutdown sender; dcpsid=,${dcpsid}`, e); }\n \n try { this.transport.close(); delete this.transport; }\n catch(e) { debugging() && console.warn(this.debugLabel, `Warning: could not close transport; dcpsid=,${dcpsid}`, e); }\n }\n } catch(error) {\n debugging() && console.warn(this.debugLabel, `could not close connection; dcpsid=${dcpsid}, url=${this.url ? this.url.href : 'unknown url'}:`, error);\n }\n finally\n {\n this.state.set(['closing', 'close-wait'], 'closed');\n this.emit('end'); /* end event resolves promises on other threads for closeWait and close (ugh) */\n }\n }\n/**\n * Prepares a non-batchable message that can be sent directly over the wire. Returns when\n * the message has been signed and is ready to be sent. The connection will not be able to send \n * any messages until the prepared message here is either sent or discarded. If 'canBatch = true',\n * will return the unsigned message instead. In this case, enqueuing is handled by\n * `async Connection.send()`, allowing the message to be put in a batch before being signed.\n * @param {...any} messageData Data to build message with. Format is:\n * `operation {string}, \n * data {Object} (optional),\n * identity {wallet.Keystore} (optional),\n * canBatch {boolean} (optional)`\n * @returns {Promise<Object>} a promise which resolves to { message, signedMessage }\n */\n\n async prepare(...messageData)\n {\n if (this.state.isNot('established'))\n {\n await this.connect().catch((e) => {\n if (e.code !== 'DCPC-1015') /* If we're closed already, then swallow the error */\n { \n this.close(e, true);\n throw e;\n }\n });\n }\n \n \n let signedMessage, message = messageData[0];\n let canBatch = false;\n \n if (typeof messageData[messageData.length - 1] === 'boolean')\n canBatch = messageData.pop();\n \n if (!message.id)\n {\n message = this.Request.buildMessage(...messageData);\n }\n \n debugging('connection') && console.debug(`${this.debugLabel} Created message ${message.id}.`);\n \n message.ackToken = this.sender.makeAckToken();\n message.batchable = canBatch;\n \n if (canBatch)\n return Promise.resolve(message);\n \n debugging('connection') && console.debug(`${this.debugLabel} Preparing message ${message.id} for sending...`); \n const messageWithNonce = await new Promise((resolve) =>\n {\n // This event is fired in the sender by serviceQueue() when the message is at the top of the queue\n // and has a nonce it can sign with. At this point, we may return the prepared message.\n this.once(`${message.id} ready`, (message) => resolve(message))\n \n // if we're enqueing a graceful close message, delay until one pass of the event loop so we can handle pending messages \n if (message.payload.operation === 'close')\n setImmediate(() => { \n this.sender.queue.push(message);\n this.sender.requestQueueService();\n }); \n else\n this.sender.queue.push(message)\n \n this.sender.requestQueueService();\n })\n \n signedMessage = await messageWithNonce.sign();\n \n debugging('connection') && console.debug(`${this.debugLabel} Finished preparing message. ${message.id} is ready to be sent.`);\n \n return { message: messageWithNonce, signedMessage: signedMessage };\n }\n\n /**\n * Sends a message to the connected peer. If the connection has not yet been established,\n * this routine will first invoke this.connect(). If the first argument has a 'signedMessage'\n * property, the message is assumed to be prepared and is sent immediately. If not, and the first\n * argument does not have an 'id' property, it will be sent to `async prepare()`, and then put\n * in the message queue.\n * \n * @param {...any} args 3 forms:\n * [operation]\n * [operation, data]\n * [operation, data, identity]\n * @returns {Promise<Response>} a promise which resolves to a response.\n */\n async send(...args)\n {\n if (!this.state.is('established'))\n await this.connect().catch((e) =>\n {\n if (e.code !== 'DCPC-1015') /* If we're closed already, then swallow the error */\n { \n this.close(e, true);\n throw e;\n }\n });\n\n let message = args[0];\n // ie. already prepared\n if (message.signedMessage)\n return this.sendPreparedMessage(message);\n \n // ie. message not hyrdated or is a response, which needs ack token\n if (!message.id || message.type === 'response')\n message = await this.prepare(...args, true);\n\n if (this.state.in(['closed']))\n throw new DCPError(`Connection (${this.id}) is ${this.state}; cannot send. (${this.loggableDest})`, 'DCPC-1001');\n \n return this.sender.enqueue(message);\n }\n \n /**\n * Set the sender's flight deck with the given message and send it.\n * Can only be passed a prepared message, which is a promise that only\n * resolves to a message when it is signed with the nonce, so it must\n * be the next message to be sent (or discarded).\n * @param {Object} messageObject\n * @returns {Promise<Response>} \n */\n sendPreparedMessage(messageObject)\n {\n if (!messageObject.signedMessage) return;\n \n const { message, signedMessage } = messageObject;\n assert(!this.sender.inFlight);\n this.sender.inFlight = { message: message, signedMessage: signedMessage };\n const messageSentPromise = this.messageLedger.addMessage(message);\n this.sender.sendInFlightMessage();\n \n return messageSentPromise;\n }\n \n /**\n * Send a signed ack directly over the wire. If we get a SocketIO.Send: Not Connected error, \n * wait until we're connected and then resend the ack.\n * @param {String} ack \n */\n sendAck(ack)\n {\n try\n {\n this.transport.send(ack)\n }\n catch(error)\n {\n // Transport was lost\n if (error.code === 'DCPC-1105')\n this.once('connect', () => this.sendAck(ack));\n else\n console.error(`${this.debugLabel} Error acking message to ${this.loggableDest}: ${error}`);\n }\n }\n \n /**\n * Discard a prepared message by removing it from the queue.\n * Returns nonce to sender and provokes queue service.\n * @param {Object} messageObject { message, signedMessage } message to discard \n */\n discardMessage(messageObject)\n {\n let { message } = messageObject;\n this.sender.nonce = message.nonce;\n delete message.nonce;\n message.type = 'unhandled-message';\n this.sender.requestQueueService();\n }\n\n /**\n * This routine returns the current time for the purposes of\n * populating the Request message payload.validity.time property.\n * \n * @returns {Number} the integer number of seconds which have elapsed since the epoch\n */\n currentTime() {\n let msSinceEpoch;\n if (this.hasNtp) {\n msSinceEpoch = Date.now();\n } else {\n const msSinceLastReceipt = performance.now() - this.receiver.lastResponseTiming.receivedMs;\n msSinceEpoch = this.receiver.lastResponseTiming.time * 1000 + msSinceLastReceipt;\n }\n return Math.floor(msSinceEpoch / 1000);\n }\n\n /**\n * This method sends a keepalive to the peer, and resolves when the response has been received.\n */\n keepalive() {\n return this.send('keepalive');\n }\n}\n\n/** \n * Determine if we got the scheduler config from a secure source, eg https or local disk.\n * We assume tha all https transactions have PKI-CA verified.\n *\n * @note protocol::getSchedulerConfigLocation() is populated via node-libs/config.js or dcp-client/index.js\n *\n * @returns true or falsey\n */\nfunction determineIfSecureConfig()\n{\n var schedulerConfigLocation = (__webpack_require__(/*! dcp/protocol-v4 */ \"./src/protocol-v4/index.js\").getSchedulerConfigLocation)();\n var schedulerConfigSecure;\n\n if (schedulerConfigLocation && (schedulerConfigLocation.protocol === 'https:' || schedulerConfigLocation.protocol === 'file:'))\n {\n debugging('strict-mode') && console.debug(`connection: scheduler config location ${schedulerConfigLocation} is secure`); /* from casual eavesdropping */\n schedulerConfigSecure = true;\n }\n\n if (isDebugBuild)\n {\n debugging('strict-mode') && console.debug('connection: scheduler config location is always secure for debug builds');\n schedulerConfigSecure = 'debug';\n }\n\n debugging('strict-mode') && console.debug(`connection: Config Location ${schedulerConfigLocation} is ${!schedulerConfigSecure ? 'not secure' : 'secure-' + schedulerConfigSecure}`);\n return schedulerConfigSecure;\n}\n\n/**\n * Determine if a URL is secure by examinining the protocol, connection, and information about the \n * process; in particular, we try to determine if the dcp config was securely provided, because if \n * it wasn't, then we can't have a secure location, since the origin could be compromised.\n * \n * \"Secure\" in this case means \"secure against casual eavesdropping\", and this information should only\n * be used to refuse to send secrets over the transport or similar.\n *\n * @returns true or falsey\n */\nfunction determineIfSecureLocation(conn)\n{\n var isSecureConfig = determineIfSecureConfig();\n var secureLocation;\n\n if (!isSecureConfig) /* can't have a secure location without a secure configuration */\n return null;\n \n if (isDebugBuild || conn.url.protocol === 'https:' || conn.url.protocol === 'tcps:')\n secureLocation = true;\n else if (conn.role === role.initiator && conn.target.hasOwnProperty('friendLocation') && conn.url === conn.target.friendLocation)\n secureLocation = true;\n else if (conn.options.allowUnencryptedSecrets)\n secureLocation = 'override';\n else\n secureLocation = false;\n\n debugging('strict-mode') && console.debug(`connection: Location ${conn.url} is ${!secureLocation ? 'not secure' : 'secure-' + secureLocation}`);\n \n return secureLocation;\n}\nexports.Connection = Connection;\n\n\n//# sourceURL=webpack://dcp/./src/protocol-v4/connection/connection.js?");
|
|
4615
4699
|
|
|
4616
4700
|
/***/ }),
|
|
4617
4701
|
|
|
@@ -4698,7 +4782,7 @@ eval("/**\n * @file protocol/connection/response.js\n * @author Ryan
|
|
|
4698
4782
|
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
4699
4783
|
|
|
4700
4784
|
"use strict";
|
|
4701
|
-
eval("/**\n * @file protocol/connection/sender.js\n * @author Ryan Rossiter, ryan@kingsds.network\n * @date January 2020\n *\n * The sender class is responsible for accepting Connection.Message instances,\n * and sending them to the peer via the provided transport instance.\n * Messages are queued in an array, and are sent in FIFO order - with the exception\n * of requests being skipped until there is a nonce available to send them with.\n */\n\n\nconst semver = __webpack_require__(/*! semver */ \"./node_modules/semver/semver.js\");\nconst DCP_ENV = __webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\");\nconst { assert } = __webpack_require__(/*! dcp/common/dcp-assert */ \"./src/common/dcp-assert.js\");\nconst { a$sleepMs } = __webpack_require__(/*! dcp/utils */ \"./src/utils/index.js\");\nconst debugging = (__webpack_require__(/*! dcp/debugging */ \"./src/debugging.js\").scope)('dcp');\nconst { DCPError } = __webpack_require__(/*! dcp/common/dcp-error */ \"./src/common/dcp-error.js\");\nconst { setImmediate } = __webpack_require__(/*! dcp/common/dcp-timers */ \"./src/common/dcp-timers.js\");\nconst { role } = __webpack_require__(/*! ./connection-constants */ \"./src/protocol-v4/connection/connection-constants.js\");\n\nlet nanoid;\nif (DCP_ENV.platform === 'nodejs') {\n const { requireNative } = __webpack_require__(/*! dcp/dcp-client/webpack-native-bridge */ \"./src/dcp-client/webpack-native-bridge.js\");\n nanoid = requireNative('nanoid').nanoid;\n} else {\n nanoid = (__webpack_require__(/*! nanoid */ \"./node_modules/nanoid/index.browser.js\").nanoid);\n}\n\nclass Sender {\n constructor(connection) {\n this.connection = connection;\n\n this.queue = [];\n this.servicingQueue = false;\n this.inFlight = null;\n this.nonce = null;\n this._ackTokenId = 0;\n \n this.debugLabel = this.connection.role === role.initiator ? `sender(i#${this.connection.id}):` : `sender(t#${this.connection.id}):`;\n }\n\n /**\n * We generate a unique token with each message we send so that \n * the peer can quickly ack the message and prove identity/uniqueness\n */\n makeAckToken() {\n return `${this.connection.id}-${this._ackTokenId++}-${nanoid()}`;\n }\n\n /**\n * Initiates the session by sending the 'connect' operation.\n * Once the response has been received, the session is established.\n * Lots of one-off code in here since it's difficult to re-use the \n * same methods meant for normal messages.\n * \n * @returns {Object} Session info\n */\n async establish() {\n assert(this.connection.transport);\n const halfSid = nanoid();\n const initiatorVersion = this.connection.constructor.VERSION;\n const connectRequest = new this.connection.Request('connect', {\n version: initiatorVersion,\n sid: halfSid,\n });\n connectRequest.id = `${this.connection.id}-connect-${nanoid()}`;\n debugging('sender') && console.debug(this.debugLabel, 'sending initial connect message');\n const resp = await this.specialFirstSend(connectRequest);\n if (typeof resp.payload !== 'object')\n throw new DCPError('Target responded to establish message without payload', 'DCPC-1018');\n if (resp.payload instanceof this.connection.ErrorPayload) {\n assert(resp.payload.type === 'protocol');\n throw new DCPError(resp.payload.message, resp.payload.code);\n }\n const targetVersion = resp.payload.version;\n const initiatorCompatibility = this.connection.constructor.VERSION_COMPATIBILITY;\n const versionCompatible = semver.satisfies(targetVersion, initiatorCompatibility);\n if (!versionCompatible) {\n debugging('sender') && console.log(this.debugLabel, `Target's version (${targetVersion}) is not compatible (must meet ${initiatorCompatibility})`);\n throw new DCPError(`Target's version (${targetVersion}) is not compatible (must meet ${initiatorCompatibility})`, 'DCPC-ETARGETVERSION');\n }\n\n // Memoize the peer version onto the Connection\n this.connection.peerVersion = targetVersion;\n if (this.connection.transport)\n this.connection.transport.peerVersion = targetVersion;\n\n // verify that our dcpsid half is still there, followed by a string\n // provided by the target that is at least the same length.\n // escape special chars:\n // eslint-disable-next-line no-useless-escape\n const regexSafeSid = halfSid.replace(/[-\\/\\\\^$*+?.()|[\\\\]{}]/g, '\\\\$&');\n const sidRegex = new RegExp(`^${regexSafeSid}.{${halfSid.length},}$`);\n if (typeof resp.dcpsid !== 'string' || !resp.dcpsid.match(sidRegex)) {\n throw new DCPError(`Target responded with invalid DCPSID: ${resp.dcpsid}`, 'DCPC-1008');\n }\n debugging('sender') && console.debug(this.debugLabel, 'connection established.');\n return {\n dcpsid: resp.dcpsid,\n peerAddress: resp.owner,\n };\n }\n\n /**\n * Invoked when the connection class becomes aware that a transport is available upon which\n * we can deliver traffic, this method either re-sends the current in-flight message, services\n * the queue if there are messages pending, or sends a keepalive to pump the queue if there's nothing\n * to send.\n */\n notifyTransportReady()\n {\n debugging('sender') && console.debug(this.debugLabel, `Notified transport is ready. inflight ${!!this.inFlight}, state ${this.connection.state}`)\n if (this.inFlight)\n this.resendInFlightMessage();\n else if (this.queue.length > 0)\n this.requestQueueService();\n else\n this.connection.keepalive().catch((error) => \n {\n if (error.code === 'ENODCPSID')\n {\n /* If our target crashed and restarted with no memory of previous connections, it will reject our message\n with the above code. Our connection will already be closing, so swallow and log this error. */\n debugging('connection') && console.debug(`${this.debugLabel} Target connection memo's corrupted. Keepalive rejected with ${error.code} `); \n }\n });\n }\n\n /**\n * Invoked if we regain a transport connection while having a message in flight.\n * Ensures that we have a signed message to send before calling `sendInFlightMessage()`\n */\n async resendInFlightMessage()\n {\n if (this.inFlight.message && !this.inFlight.signedMessage)\n this.inFlight.signedMessage = await this.inFlight.message.sign();\n \n assert(this.inFlight.signedMessage);\n this.sendInFlightMessage();\n }\n \n /**\n * We cannot use the normal enqueue logic for the first message\n * and we need many of the same bits of logic (but not all) so\n * this is the place for that kind of one-off logic.\n * We finish preparing the 'connect' request, put it in flight and send it out.\n * @param {Message} message \n */\n async specialFirstSend(message) { /* XXXwg - special first send *will* cause double dcpsid if invoked twice */\n message.ackToken = this.makeAckToken();\n let signedMessage = await message.sign();\n const messageSentPromise = this.connection.messageLedger.addMessage(message);\n \n this.inFlight = { message: message, signedMessage: signedMessage };\n if (message instanceof this.connection.Response) {\n this.connection.registerConnectResponse(this.inFlight.message); /* XXXwg todo - audit this.inFlight */\n }\n this.sendInFlightMessage();\n return messageSentPromise;\n }\n\n /**\n * Places message into `queue` but only schedules queue to be serviced\n * if connection is ready to send. Otherwise other tools will have to\n * handle making it ready to send again.\n * @param {Connection.Request|Connection.Response} message\n * @returns {Promise<Connection.Response>} from messageLedger that resolves when message response received.\n */\n enqueue(message) {\n if (message.payload && message.payload.operation)\n debugging('connection') && console.debug(this.debugLabel, `enqueueing ${message.type} message for operation:`, message.payload.operation);\n else\n debugging('connection') && console.debug(this.debugLabel, `enqueueing ${message.type} message`);\n \n this.queue.push(message);\n \n if (this.connection.state.in(['initial', 'disconnected']))\n this.connection.connect();\n\n setImmediate(() => this.requestQueueService());\n return this.connection.messageLedger.addMessage(message);\n }\n \n /**\n * Checks if we're able to send a message in our current state, and\n * services the queue if so.\n */\n requestQueueService()\n {\n if (this.inFlight || !this.nonce || this.servicingQueue)\n {\n debugging('sender') && debugging('verbose') && console.debug(\n `${this.debugLabel} request for queue service denied.`,\n `inFlight: ${this.inFlight && this.inFlight.message && this.inFlight.message.id},`,\n `nonce: ${this.nonce}, queue running: ${this.servicingQueue}`\n );\n return;\n }\n else\n {\n debugging('sender') && console.debug(`${this.debugLabel} request for queue service accepted.`)\n this.serviceQueue();\n }\n }\n /**\n * Pulls a message from the queue. If the message is batchable, pauses for 30ms to allow other batchable\n * messages to enter the queue, and then continues dequeuing messages. \n * If the message is not batchable, ie. is here just to finish being prepared, then sign the message and emit\n * `message.id ready` to resolve the `Connection.prepareMessage` promise for the dequeued message.\n * When a message is signed, our nonce is deleted so that it may not be used in another message.\n */\n serviceQueue ()\n {\n if (!this.inFlight && this.connection.state.in(['established', 'closing', 'close-wait']))\n {\n debugging('sender') && console.debug(`${this.debugLabel} serviceQueue: checking queue`)\n this.servicingQueue = true;\n let nextMessage = this.queue.shift();\n if (!nextMessage)\n {\n debugging('sender') && console.debug(`${this.debugLabel} serviceQueue: nothing to send`)\n this.servicingQueue = false;\n return;\n }\n \n if (nextMessage.batchable)\n {\n debugging('sender') && console.debug(`${this.debugLabel} serviceQueue: pulled batchable message, allowing more messages to enter queue`)\n // Buffer the dequeuing of batchable messages to emulate the intention of Nagle's\n // algorithm, used to reduce the number of packets being sent over the network.\n a$sleepMs(this.connection.options.messageBatchingBuffer * 1000)\n .then(() =>\n {\n let rawBatch = [];\n do\n {\n rawBatch.push(nextMessage);\n nextMessage = this.queue.shift();\n } while (nextMessage && nextMessage.batchable && rawBatch.length < this.maxBatchSize)\n // if dequeuing a prepared message stopped the loop, put the message back in front \n if (nextMessage) this.queue.unshift(nextMessage);\n \n this.servicingQueue = false;\n debugging('sender') && console.debug(`${this.debugLabel} serviceQueue: pulled ${rawBatch.length} messages for batching. done servicing queue`)\n this.createAndSend(rawBatch);\n });\n }\n else\n {\n nextMessage.nonce = this.nonce;\n delete this.nonce;\n \n this.connection.emit(`${nextMessage.id} ready`, nextMessage);\n this.servicingQueue = false;\n debugging('sender') && console.debug(`${this.debugLabel} serviceQueue: pulled non-batchable message, finished its preparation. done servicing queue`)\n }\n \n }\n else\n {\n debugging('connection') && console.debug(this.debugLabel, `ignoring call to service queue. inFlight=${!!this.inFlight} state=${this.connection.state.valueOf()}`);\n }\n }\n\n /**\n * Takes an array of messages and batches them if there's more than one. Signs the resultant message and puts it in flight.\n * @param {Array<Connection.Message>} [messages] Array of messages to batch together for a transmission. \n * if not provided, draws from queue.\n *\n * @returns a Promise which resolves to a number which represents the number of messages sent. This is \n * either 0, 1, or the number of messages in a batch (possibly 1).\n */\n async createAndSend(messages) {\n assert(!this.inFlight);\n \n // By default, let `message` be first entry in input array.\n // If more than one entry in array, reassign `message` as batch object.\n let message = messages[0];\n if (messages.length > 1)\n message = new this.connection.Batch(messages, this.makeAckToken()); \n\n message.nonce = this.nonce;\n delete this.nonce;\n \n if (message instanceof this.connection.Batch) this.connection.messageLedger.addBatch(message);\n \n this.inFlight = { message: message };\n this.inFlight.signedMessage = await message.sign();\n \n debugging('connection') && console.debug(this.debugLabel, `sending a signed ${message.type} of ${messages.length}`);\n this.sendInFlightMessage();\n }\n \n /**\n * Sends the message stored in the `inFlight` var over the transport.\n * `clearFlightDeck` is the only method that should be resetting this.inFlight\n * and thus closing the loop.\n */\n sendInFlightMessage()\n {\n assert(this.inFlight);\n\n debugging('sender') && console.debug(this.debugLabel, `sending in-flight message ${this.inFlight.message.id}`);\n\n try\n {\n const message = this.inFlight.message;\n let type;\n \n /** XXXwg todo - figure out why Request/Response have name=Message and get rid of tests */\n if (message instanceof this.connection.Request)\n type = 'Request';\n else if (message instanceof this.connection.Response)\n type = 'Response';\n else \n type = message.constructor.name;\n\n if (!this.connection.transport)\n {\n debugging('sender') && console.debug(this.debugLabel, `no transport for ${type}; not sending`, this.inFlight.message.id,\n `(${this.inFlight.signedMessage.length} bytes)`);\n\n return;\n }\n \n \n debugging('sender') && console.debug(this.debugLabel, `sending ${type}`, this.inFlight.message.id,\n `(${this.inFlight.signedMessage.length} bytes)`);\n\n if (type === 'Batch')\n type += ':' + (this.inFlight.message.messages.length);\n debugging('wire') && console.debug(this.connection.debugLabel, `sendInFlightMessage: ${String(type).padEnd(10, ' ')} -> ${this.connection.loggableDest}`);\n this.connection.transport.send(this.inFlight.signedMessage);\n this.connection.emit('send', this.inFlight);\n }\n catch (error)\n {\n console.error(`Error while sending message ${this.inFlight.message.id} to ${this.connection.loggableDest}:`, error);\n debugging('sender') && console.debug(this.debugLabel, 'call stack:', new Error().stack);\n }\n }\n\n /**\n * Clear a message from the flight deck. For the foreseeable future the deck\n * only holds one message at a time, so we just assert that it matches.\n * @param {Connection.Message} message message that can be cleared from the flight deck\n */\n clearFlightDeck(message, nonce) {\n if (this.inFlight !== null) {\n debugging('sender') && console.debug(this.debugLabel, 'clearing flight deck. nonce =', nonce);\n assert(message === this.inFlight.message);\n this.inFlight = null;\n this.nonce = nonce;\n this.requestQueueService();\n }\n }\n\n /**\n * When a connection is closed the sender needs to cancel its send efforts.\n */\n shutdown() {\n debugging('sender') && console.debug(this.debugLabel, 'shutting down.');\n this.inFlight = null;\n this.nonce = null;\n this.queue = [];\n }\n\n // When allowBatch=false, set the batchSize to 1 so each\n // message gets sent individually (not in a batch)\n get maxBatchSize() {\n if (this._maxBatchSize) return this._maxBatchSize;\n const maxBatchSize = Math.max(this.connection.options.maxMessagesPerBatch, 1);\n this._maxBatchSize = this.connection.options.allowBatch ? maxBatchSize : 1;\n return this._maxBatchSize;\n }\n}\n\nObject.assign(module.exports, {\n Sender,\n});\n\n\n//# sourceURL=webpack://dcp/./src/protocol-v4/connection/sender.js?");
|
|
4785
|
+
eval("/**\n * @file protocol/connection/sender.js\n * @author Ryan Rossiter, ryan@kingsds.network\n * @date January 2020\n *\n * The sender class is responsible for accepting Connection.Message instances,\n * and sending them to the peer via the provided transport instance.\n * Messages are queued in an array, and are sent in FIFO order - with the exception\n * of requests being skipped until there is a nonce available to send them with.\n */\n\n\nconst semver = __webpack_require__(/*! semver */ \"./node_modules/semver/semver.js\");\nconst DCP_ENV = __webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\");\nconst { assert } = __webpack_require__(/*! dcp/common/dcp-assert */ \"./src/common/dcp-assert.js\");\nconst { a$sleepMs } = __webpack_require__(/*! dcp/utils */ \"./src/utils/index.js\");\nconst debugging = (__webpack_require__(/*! dcp/debugging */ \"./src/debugging.js\").scope)('dcp');\nconst { DCPError } = __webpack_require__(/*! dcp/common/dcp-error */ \"./src/common/dcp-error.js\");\nconst { setImmediate } = __webpack_require__(/*! dcp/common/dcp-timers */ \"./src/common/dcp-timers.js\");\nconst { role } = __webpack_require__(/*! ./connection-constants */ \"./src/protocol-v4/connection/connection-constants.js\");\n\nlet nanoid;\nif (DCP_ENV.platform === 'nodejs') {\n const { requireNative } = __webpack_require__(/*! dcp/dcp-client/webpack-native-bridge */ \"./src/dcp-client/webpack-native-bridge.js\");\n nanoid = requireNative('nanoid').nanoid;\n} else {\n nanoid = (__webpack_require__(/*! nanoid */ \"./node_modules/nanoid/index.browser.js\").nanoid);\n}\n\nclass Sender {\n constructor(connection) {\n this.connection = connection;\n\n this.queue = [];\n this.servicingQueue = false;\n this.inFlight = null;\n this.nonce = null;\n this._ackTokenId = 0;\n \n this.debugLabel = this.connection.role === role.initiator ? `sender(i#${this.connection.id}):` : `sender(t#${this.connection.id}):`;\n }\n\n /**\n * We generate a unique token with each message we send so that \n * the peer can quickly ack the message and prove identity/uniqueness\n */\n makeAckToken() {\n return `${this.connection.id}-${this._ackTokenId++}-${nanoid()}`;\n }\n\n /**\n * Initiates the session by sending the 'connect' operation.\n * Once the response has been received, the session is established.\n * Lots of one-off code in here since it's difficult to re-use the \n * same methods meant for normal messages.\n * \n * @returns {Object} Session info\n */\n async establish() {\n assert(this.connection.transport);\n const halfSid = nanoid();\n const initiatorVersion = this.connection.constructor.VERSION;\n const connectRequest = new this.connection.Request('connect', {\n version: initiatorVersion,\n sid: halfSid,\n });\n connectRequest.id = `${this.connection.id}-connect-${nanoid()}`;\n debugging('sender') && console.debug(this.debugLabel, 'sending initial connect message');\n const resp = await this.specialFirstSend(connectRequest);\n if (typeof resp.payload !== 'object')\n throw new DCPError('Target responded to establish message without payload', 'DCPC-1018');\n if (resp.payload instanceof this.connection.ErrorPayload) {\n assert(resp.payload.type === 'protocol');\n throw new DCPError(resp.payload.message, resp.payload.code);\n }\n const targetVersion = resp.payload.version;\n const initiatorCompatibility = this.connection.constructor.VERSION_COMPATIBILITY;\n const versionCompatible = semver.satisfies(targetVersion, initiatorCompatibility);\n if (!versionCompatible) {\n debugging('sender') && console.log(this.debugLabel, `Target's version (${targetVersion}) is not compatible (must meet ${initiatorCompatibility})`);\n throw new DCPError(`Target's version (${targetVersion}) is not compatible (must meet ${initiatorCompatibility})`, 'DCPC-ETARGETVERSION');\n }\n\n // Memoize the peer version onto the Connection\n this.connection.peerVersion = targetVersion;\n if (this.connection.transport)\n this.connection.transport.peerVersion = targetVersion;\n\n // verify that our dcpsid half is still there, followed by a string\n // provided by the target that is at least the same length.\n // escape special chars:\n // eslint-disable-next-line no-useless-escape\n const regexSafeSid = halfSid.replace(/[-\\/\\\\^$*+?.()|[\\\\]{}]/g, '\\\\$&');\n const sidRegex = new RegExp(`^${regexSafeSid}.{${halfSid.length},}$`);\n if (typeof resp.dcpsid !== 'string' || !resp.dcpsid.match(sidRegex)) {\n throw new DCPError(`Target responded with invalid DCPSID: ${resp.dcpsid}`, 'DCPC-1008');\n }\n debugging('sender') && console.debug(this.debugLabel, 'connection established.');\n return {\n dcpsid: resp.dcpsid,\n peerAddress: resp.owner,\n };\n }\n\n /**\n * Invoked when the connection class becomes aware that a transport is available upon which\n * we can deliver traffic, this method either re-sends the current in-flight message, services\n * the queue if there are messages pending, or sends a keepalive to pump the queue if there's nothing\n * to send.\n */\n notifyTransportReady()\n {\n debugging('sender') && console.debug(this.debugLabel, `Notified transport is ready. inflight ${!!this.inFlight}, state ${this.connection.state}`)\n if (this.inFlight)\n this.resendInFlightMessage();\n else if (this.queue.length > 0)\n this.requestQueueService();\n else\n this.connection.keepalive().catch((error) => \n {\n if (error.code === 'ENODCPSID')\n {\n /* If our target crashed and restarted with no memory of previous connections, it will reject our message\n with the above code. Our connection will already be closing, so swallow and log this error. */\n debugging('connection') && console.debug(`${this.debugLabel} Target connection memo's corrupted. Keepalive rejected with ${error.code} `); \n }\n });\n }\n\n /**\n * Invoked if we regain a transport connection while having a message in flight.\n * Ensures that we have a signed message to send before calling `sendInFlightMessage()`\n */\n async resendInFlightMessage()\n {\n if (this.inFlight.message && !this.inFlight.signedMessage)\n this.inFlight.signedMessage = await this.inFlight.message.sign();\n \n assert(this.inFlight.signedMessage);\n this.sendInFlightMessage();\n }\n \n /**\n * We cannot use the normal enqueue logic for the first message\n * and we need many of the same bits of logic (but not all) so\n * this is the place for that kind of one-off logic.\n * We finish preparing the 'connect' request, put it in flight and send it out.\n * @param {Message} message \n */\n async specialFirstSend(message) { /* XXXwg - special first send *will* cause double dcpsid if invoked twice */\n message.ackToken = this.makeAckToken();\n let signedMessage = await message.sign();\n const messageSentPromise = this.connection.messageLedger.addMessage(message);\n \n this.inFlight = { message: message, signedMessage: signedMessage };\n if (message instanceof this.connection.Response) {\n this.connection.registerConnectResponse(this.inFlight.message); /* XXXwg todo - audit this.inFlight */\n }\n this.sendInFlightMessage();\n return messageSentPromise;\n }\n\n /**\n * Places message into `queue` but only schedules queue to be serviced\n * if connection is ready to send. Otherwise other tools will have to\n * handle making it ready to send again.\n * @param {Connection.Request|Connection.Response} message\n * @returns {Promise<Connection.Response>} from messageLedger that resolves when message response received.\n */\n enqueue(message) {\n if (message.payload && message.payload.operation)\n debugging('connection') && console.debug(this.debugLabel, `enqueueing ${message.type} message for operation:`, message.payload.operation);\n else\n debugging('connection') && console.debug(this.debugLabel, `enqueueing ${message.type} message`);\n \n this.queue.push(message);\n \n if (this.connection.state.in(['initial', 'disconnected']))\n this.connection.connect();\n\n setImmediate(() => this.requestQueueService());\n return this.connection.messageLedger.addMessage(message);\n }\n \n /**\n * Checks if we're able to send a message in our current state, and\n * services the queue if so.\n */\n requestQueueService()\n {\n if (this.inFlight || !this.nonce || this.servicingQueue)\n {\n debugging('sender') && debugging('verbose') && console.debug(\n `${this.debugLabel} request for queue service denied.`,\n `inFlight: ${this.inFlight && this.inFlight.message && this.inFlight.message.id},`,\n `nonce: ${this.nonce}, queue running: ${this.servicingQueue}`\n );\n return;\n }\n else\n {\n debugging('sender') && console.debug(`${this.debugLabel} request for queue service accepted.`)\n this.serviceQueue();\n }\n }\n /**\n * Pulls a message from the queue. If the message is batchable, pauses for 30ms to allow other batchable\n * messages to enter the queue, and then continues dequeuing messages. \n * If the message is not batchable, ie. is here just to finish being prepared, then sign the message and emit\n * `message.id ready` to resolve the `Connection.prepareMessage` promise for the dequeued message.\n * When a message is signed, our nonce is deleted so that it may not be used in another message.\n */\n serviceQueue ()\n {\n if (!this.inFlight && this.connection.state.in(['established', 'closing', 'close-wait']))\n {\n debugging('sender') && console.debug(`${this.debugLabel} serviceQueue: checking queue`)\n this.servicingQueue = true;\n let nextMessage = this.queue.shift();\n if (!nextMessage)\n {\n debugging('sender') && console.debug(`${this.debugLabel} serviceQueue: nothing to send`)\n this.servicingQueue = false;\n return;\n }\n \n if (nextMessage.batchable)\n {\n debugging('sender') && console.debug(`${this.debugLabel} serviceQueue: pulled batchable message, allowing more messages to enter queue`)\n // Buffer the dequeuing of batchable messages to emulate the intention of Nagle's\n // algorithm, used to reduce the number of packets being sent over the network.\n a$sleepMs(this.connection.options.messageBatchingBuffer * 1000)\n .then(() =>\n {\n let rawBatch = [];\n do\n {\n rawBatch.push(nextMessage);\n nextMessage = this.queue.shift();\n } while (nextMessage && nextMessage.batchable && rawBatch.length < this.maxBatchSize)\n // if dequeuing a prepared message stopped the loop, put the message back in front \n if (nextMessage) this.queue.unshift(nextMessage);\n \n this.servicingQueue = false;\n debugging('sender') && console.debug(`${this.debugLabel} serviceQueue: pulled ${rawBatch.length} messages for batching. done servicing queue`)\n this.createAndSend(rawBatch);\n });\n }\n else\n {\n nextMessage.nonce = this.nonce;\n delete this.nonce;\n \n this.connection.emit(`${nextMessage.id} ready`, nextMessage);\n this.servicingQueue = false;\n debugging('sender') && console.debug(`${this.debugLabel} serviceQueue: pulled non-batchable message, finished its preparation. done servicing queue`)\n }\n \n }\n else\n {\n debugging('connection') && console.debug(this.debugLabel, `ignoring call to service queue. inFlight=${!!this.inFlight} state=${this.connection.state.valueOf()}`);\n }\n }\n\n /**\n * Takes an array of messages and batches them if there's more than one. Signs the resultant message and puts it in flight.\n * @param {Array<Connection.Message>} [messages] Array of messages to batch together for a transmission. \n * if not provided, draws from queue.\n *\n * @returns a Promise which resolves to a number which represents the number of messages sent. This is \n * either 0, 1, or the number of messages in a batch (possibly 1).\n */\n async createAndSend(messages) {\n assert(!this.inFlight);\n \n // By default, let `message` be first entry in input array.\n // If more than one entry in array, reassign `message` as batch object.\n let message = messages[0];\n if (messages.length > 1)\n message = new this.connection.Batch(messages, this.makeAckToken()); \n\n message.nonce = this.nonce;\n delete this.nonce;\n \n if (message instanceof this.connection.Batch) this.connection.messageLedger.addBatch(message);\n \n this.inFlight = { message: message };\n this.inFlight.signedMessage = await message.sign();\n \n debugging('connection') && console.debug(this.debugLabel, `sending a signed ${message.type} of ${messages.length}`);\n this.sendInFlightMessage();\n }\n \n /**\n * Sends the message stored in the `inFlight` var over the transport.\n * `clearFlightDeck` is the only method that should be resetting this.inFlight\n * and thus closing the loop.\n */\n sendInFlightMessage()\n {\n assert(this.inFlight);\n\n debugging('sender') && console.debug(this.debugLabel, `sending in-flight message ${this.inFlight.message.id}`);\n\n try\n {\n const message = this.inFlight.message;\n let type;\n \n /** XXXwg todo - figure out why Request/Response have name=Message and get rid of tests */\n if (message instanceof this.connection.Request)\n type = 'Request';\n else if (message instanceof this.connection.Response)\n type = 'Response';\n else \n type = message.constructor.name;\n\n if (!this.connection.transport)\n {\n debugging('sender') && console.debug(this.debugLabel, `no transport for ${type}; not sending`, this.inFlight.message.id,\n `(${this.inFlight.signedMessage.length} bytes)`);\n\n return;\n }\n \n \n debugging('sender') && console.debug(this.debugLabel, `sending ${type}`, this.inFlight.message.id,\n `(${this.inFlight.signedMessage.length} bytes)`);\n\n if (type === 'Batch')\n type += ':' + (this.inFlight.message.messages.length);\n debugging('wire') && console.debug(this.connection.debugLabel, `sendInFlightMessage: ${String(type).padEnd(10, ' ')} -> ${this.connection.loggableDest}`);\n\n let transmitAttempts = this.inFlight.message.transmitAttempts;\n // if message transmit attempts hits limit -> close connection\n if (transmitAttempts && transmitAttempts >= this.connection.options.messageTransmissionTolerance)\n {\n debugging('sender') && console.debug(this.debugLabel, `sendInFlightMessage: message ${this.inFlight.message.id} transmission attempts hit tolerance of ${this.connection.options.messageTransmissionTolerance}`)\n this.connection.handleTooManyMessageTransmissions();\n return;\n }\n \n if (!transmitAttempts)\n this.inFlight.message.transmitAttempts = 0;\n this.inFlight.message.transmitAttempts += 1;\n \n debugging('sender') && console.debug(this.debugLabel, `message transmit attempt #${this.inFlight.message.transmitAttempts}`);\n\n this.connection.transport.send(this.inFlight.signedMessage);\n this.connection.emit('send', this.inFlight);\n }\n catch (error)\n {\n console.error(`Error while sending message ${this.inFlight.message.id} to ${this.connection.loggableDest}:`, error);\n debugging('sender') && console.debug(this.debugLabel, 'call stack:', new Error().stack);\n }\n }\n\n /**\n * Clear a message from the flight deck. For the foreseeable future the deck\n * only holds one message at a time, so we just assert that it matches.\n * @param {Connection.Message} message message that can be cleared from the flight deck\n */\n clearFlightDeck(message, nonce) {\n if (this.inFlight !== null) {\n debugging('sender') && console.debug(this.debugLabel, 'clearing flight deck. nonce =', nonce);\n assert(message === this.inFlight.message);\n this.inFlight = null;\n this.nonce = nonce;\n this.requestQueueService();\n }\n }\n\n /**\n * When a connection is closed the sender needs to cancel its send efforts.\n */\n shutdown() {\n debugging('sender') && console.debug(this.debugLabel, 'shutting down.');\n this.inFlight = null;\n this.nonce = null;\n this.queue = [];\n }\n\n // When allowBatch=false, set the batchSize to 1 so each\n // message gets sent individually (not in a batch)\n get maxBatchSize() {\n if (this._maxBatchSize) return this._maxBatchSize;\n const maxBatchSize = Math.max(this.connection.options.maxMessagesPerBatch, 1);\n this._maxBatchSize = this.connection.options.allowBatch ? maxBatchSize : 1;\n return this._maxBatchSize;\n }\n}\n\nObject.assign(module.exports, {\n Sender,\n});\n\n\n//# sourceURL=webpack://dcp/./src/protocol-v4/connection/sender.js?");
|
|
4702
4786
|
|
|
4703
4787
|
/***/ }),
|
|
4704
4788
|
|
|
@@ -4880,7 +4964,7 @@ eval("/**\n * @file utils/http.js Helper module for things rel
|
|
|
4880
4964
|
/***/ ((module, exports, __webpack_require__) => {
|
|
4881
4965
|
|
|
4882
4966
|
"use strict";
|
|
4883
|
-
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 * @date Feb 2020\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 */\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\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__(/*! ./message-to-buffer */ \"./src/utils/message-to-buffer.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 = (DCP_ENV.platform === 'nodejs') ? requireNative('nanoid') : __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(() => { 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} 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').constructor({enabled: exports.useChalk});\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 * 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 util$$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 = 0) {\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 return str ? str.slice(0, truncationLength) : null;\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, false /* skipFirst*/, 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, false /* skipFirst*/, digits);\n}\n\n/**\n * @param {object} jobMap\n * @param {boolean} [skipFirst=false]\n * @param {number} [digits=-1]\n * @returns {string}\n */\nexports.compressJobMap = function utils$$compressJobMap(jobMap, skipFirst = false, digits = -1) {\n return exports.compressJobArray(Object.entries(jobMap), skipFirst, digits);\n}\n\n/**\n * @param {object[]} jobArray\n * @param {boolean} [skipFirst=false]\n * @param {number} [digits=-1]\n * @returns {string}\n */\nexports.compressJobArray = function utils$$compressJobArray(jobArray, skipFirst = false, digits = -1) {\n let output = '';\n for (let k = 0; k < jobArray.length; k++) {\n output += exports.compressJobValue(jobArray[k], skipFirst, digits);\n }\n return output;\n}\n\n/**\n * @param {object|object[]} jobValue\n * @param {boolean} [skipFirst=false]\n * @param {number} [digits=-1]\n * @returns {string}\n */\nexports.compressJobValue = function utils$$compressJobValue(jobValue, skipFirst = false, digits = -1) {\n if (jobValue.job && jobValue.slices)\n return exports.compressJobEntry(jobValue.job, (skipFirst ? jobValue.slices.slice(1) : jobValue.slices), digits);\n if (jobValue.length === 2)\n return exports.compressJobEntry(jobValue[0], (skipFirst ? jobValue[1].slice(1) : jobValue[1]), digits);\n return 'nada';\n}\n\n/**\n * @param {*} jobAddress\n * @param {Array<number|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) : fragment;\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|Number>} numberArray\n * @returns {string}\n */\nexports.compressRange = function utils$$compressRange(numberArray) {\n assert(numberArray && Array.isArray(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.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 * shuffle\n * hashGeneration\n *********************************************************************************************/\n\nexports.shuffle = function utils$$shuffle(jobDescriptors, partitionPortions) {\n const schedulerConstants = __webpack_require__(/*! dcp/common/scheduler-constants */ \"./src/common/scheduler-constants.js\");\n \n let jobDescriptorsInEstimation = [];\n let partitionPortionsInEstimation = [];\n for (let [index, jobDescriptor] of jobDescriptors.entries()) {\n if(jobDescriptor.job.status === schedulerConstants.jobStatus.estimation) {\n jobDescriptorsInEstimation.push(jobDescriptor);\n jobDescriptors.splice(index, 1);\n partitionPortionsInEstimation.push(partitionPortions[index]);\n partitionPortions.splice(index, 1) \n }\n }\n \n let currentIndex = jobDescriptors.length, randomIndex;\n // While there remain elements to shuffle...\n while (0 !== currentIndex && jobDescriptors.length > 0) {\n\n // Pick a remaining element...\n randomIndex = Math.floor(Math.random() * currentIndex);\n currentIndex--;\n\n // And swap it with the current element.\n [jobDescriptors[currentIndex], jobDescriptors[randomIndex]] = [\n jobDescriptors[randomIndex], jobDescriptors[currentIndex]];\n\n [partitionPortions[currentIndex], partitionPortions[randomIndex]] = [\n partitionPortions[randomIndex], partitionPortions[currentIndex]];\n }\n \n return [\n [...jobDescriptorsInEstimation, ...jobDescriptors],\n [...partitionPortionsInEstimation, ...partitionPortions]\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 * retrieveSlicesFromJob return type used in Supervisor.\n *********************************************************************************************/\n\n/**\n * @typedef {object} SliceMessage\n * @property {string} jobAddress\n * @property {string} uuid\n * @property {string} worker\n * @property {number} sliceNumber\n * @property {boolean} isEstimationSlice\n * @property {boolean} isLongSlice\n * @property {number} timestamp\n * @property {string} resultStorageType\n * @property {string} resultStorageDetails\n * @property {object} resultStorageParams\n * @property {string} datumUri\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\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?");
|
|
4967
|
+
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 * @date Feb 2020\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 */\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\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__(/*! ./message-to-buffer */ \"./src/utils/message-to-buffer.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 = (DCP_ENV.platform === 'nodejs') ? requireNative('nanoid') : __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(() => { 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} 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').constructor({enabled: exports.useChalk});\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 = 0) {\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 return str ? str.slice(0, truncationLength) : null;\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, false /* skipFirst*/, 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, false /* skipFirst*/, digits);\n}\n\n/**\n * @param {object} jobMap\n * @param {boolean} [skipFirst=false]\n * @param {number} [digits=-1]\n * @returns {string}\n */\nexports.compressJobMap = function utils$$compressJobMap(jobMap, skipFirst = false, digits = -1) {\n return exports.compressJobArray(Object.entries(jobMap), skipFirst, digits);\n}\n\n/**\n * @param {object[]} jobArray\n * @param {boolean} [skipFirst=false]\n * @param {number} [digits=-1]\n * @returns {string}\n */\nexports.compressJobArray = function utils$$compressJobArray(jobArray, skipFirst = false, digits = -1) {\n let output = '';\n for (let k = 0; k < jobArray.length; k++) {\n output += exports.compressJobValue(jobArray[k], skipFirst, digits);\n }\n return output;\n}\n\n/**\n * @param {object|object[]} jobValue\n * @param {boolean} [skipFirst=false]\n * @param {number} [digits=-1]\n * @returns {string}\n */\nexports.compressJobValue = function utils$$compressJobValue(jobValue, skipFirst = false, digits = -1) {\n if (jobValue.job && jobValue.slices)\n return exports.compressJobEntry(jobValue.job, (skipFirst ? jobValue.slices.slice(1) : jobValue.slices), digits);\n if (jobValue.length === 2)\n return exports.compressJobEntry(jobValue[0], (skipFirst ? jobValue[1].slice(1) : jobValue[1]), digits);\n return 'nada';\n}\n\n/**\n * @param {*} jobAddress\n * @param {Array<number|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|Number>} numberArray\n * @returns {string}\n */\nexports.compressRange = function utils$$compressRange(numberArray) {\n assert(numberArray && Array.isArray(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.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 * shuffle\n * hashGeneration\n *********************************************************************************************/\n\nexports.shuffle = function utils$$shuffle(jobDescriptors, partitionPortions) {\n const schedulerConstants = __webpack_require__(/*! dcp/common/scheduler-constants */ \"./src/common/scheduler-constants.js\");\n \n let jobDescriptorsInEstimation = [];\n let partitionPortionsInEstimation = [];\n for (let [index, jobDescriptor] of jobDescriptors.entries()) {\n if(jobDescriptor.job.status === schedulerConstants.jobStatus.estimation) {\n jobDescriptorsInEstimation.push(jobDescriptor);\n jobDescriptors.splice(index, 1);\n partitionPortionsInEstimation.push(partitionPortions[index]);\n partitionPortions.splice(index, 1) \n }\n }\n \n let currentIndex = jobDescriptors.length, randomIndex;\n // While there remain elements to shuffle...\n while (0 !== currentIndex && jobDescriptors.length > 0) {\n\n // Pick a remaining element...\n randomIndex = Math.floor(Math.random() * currentIndex);\n currentIndex--;\n\n // And swap it with the current element.\n [jobDescriptors[currentIndex], jobDescriptors[randomIndex]] = [\n jobDescriptors[randomIndex], jobDescriptors[currentIndex]];\n\n [partitionPortions[currentIndex], partitionPortions[randomIndex]] = [\n partitionPortions[randomIndex], partitionPortions[currentIndex]];\n }\n \n return [\n [...jobDescriptorsInEstimation, ...jobDescriptors],\n [...partitionPortionsInEstimation, ...partitionPortions]\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 * retrieveSlicesFromJob return type used in Supervisor.\n *********************************************************************************************/\n\n/**\n * @typedef {object} SliceMessage\n * @property {string} jobAddress\n * @property {string} uuid\n * @property {string} worker\n * @property {number} sliceNumber\n * @property {boolean} isEstimationSlice\n * @property {boolean} isLongSlice\n * @property {number} timestamp\n * @property {string} resultStorageType\n * @property {string} resultStorageDetails\n * @property {object} resultStorageParams\n * @property {string} datumUri\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\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?");
|
|
4884
4968
|
|
|
4885
4969
|
/***/ }),
|
|
4886
4970
|
|
|
@@ -4902,7 +4986,7 @@ eval("/**\n * @file src/utils/inventory.js\n * Inventory met
|
|
|
4902
4986
|
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
|
4903
4987
|
|
|
4904
4988
|
"use strict";
|
|
4905
|
-
eval("/* provided dependency */ var process = __webpack_require__(/*! ./node_modules/process/browser.js */ \"./node_modules/process/browser.js\");\n/**\n * @file just-fetch.js\n * @author Ryan Rossiter <ryan@kingsds.network>\n * @date June 2020\n *\n * Cross-platform method for performing an HTTP request.\n */\n\n\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 { DcpURL } = __webpack_require__(/*! dcp/common/dcp-url */ \"./src/common/dcp-url.js\");\nconst kvin = __webpack_require__(/*! kvin */ \"./node_modules/kvin/kvin.js\");\n\n /**\n * Make a request, as a promise, resolves with a string or json object\n *\n * @param {string | object} url - path of file to be fetched; if object must be instanceof URL or DcpURL\n * @param {string} [type = \"string\"] - expected type of the response\n * @param {string} [method = \"GET\"] - type of request to make\n * @param {boolean} [bustCache = false] - whether to add cache poison to the end of the url\n * @param {any} [body] - what to send as the body of the message. Objects are turned into into \n * standard CGI key=value encoding when the method is 'POST'\n * @returns {Promise} - resolves with response (json if type=\"JSON\", string otherwise), rejects on fail\n */\nexports.justFetch = function justFetch(url, type = 'string', method = 'GET', bustCache = false, body = undefined) {\n return new Promise((resolve, reject) => {\n let deeperErrorStack = new Error().stack;\n deeperErrorStack = deeperErrorStack.substring(deeperErrorStack.indexOf('\\n') + 1);\n \n if (typeof url === 'object' && typeof url.href !== 'undefined')\n {\n assert(url.protocol !== 'data:');\n url = url.href;\n }\n\n if (bustCache)\n {\n let bustParam = ((typeof document !== 'undefined' && document.head && document.head.getAttribute('version')) || Date.now()); /* cache buster */\n\n url = new URL(url);\n url.search += (url.search ? '&' : '') + encodeURI(bustParam);\n url = url.href;\n }\n /* url now guaranteed to be a string */\n \n const xhr = new XMLHttpRequest();\n xhr.onloadend = function Protocol$$justFetch$onloadend() {\n try {\n delete xhr.onloadend;\n\n if (xhr.status >= 200 && xhr.status < 300) {\n if (xhr.getResponseHeader('content-type') && type === 'string') {\n type = xhr.getResponseHeader('content-type').split(';')[0];\n }\n \n let data = xhr.responseText;\n if (type === 'JSON' || type === 'application/json') {\n data = JSON.parse(data);\n }\n\n if (type === 'application/x-kvin' ) {\n data = kvin.deserialize(data);\n }\n \n resolve(data);\n } else {\n function makeFetchError()\n {\n if (xhr.status)\n return new Error(`HTTP Error ${xhr.status} fetching ${url}`);\n\n /* NodeJS via xmlhttprequest-ssl leaves an Error duck */\n if (xhr.statusText && xhr.statusText.code && xhr.statusText.message) {\n let error = new Error(xhr.statusText.message);\n let stack = error.stack;\n\n Object.assign(error, xhr.statusText);\n if (xhr.statusText.stack)\n error.stack = xhr.statusText.stack.split('\\n').slice(1).join('\\n') + '---\\n' + stack;\n else\n error.stack = stack;\n return error;\n }\n\n return new Error(`Network error fetching ${url}`);\n }\n\n const error = makeFetchError();\n \n error.request = Object.assign({}, xhr); /* Shallow clone so that console.log will work in node */\n error.request.method = method;\n error.request.location = url;\n error.stack += '\\n' + deeperErrorStack;\n if (xhr.status)\n error.code = 'HTTP_' + xhr.status;\n\n throw error;\n }\n } catch (e) {\n reject(e);\n }\n };\n\n xhr.open(method, url);\n if (!body)\n xhr.send();\n else\n {\n if (typeof body === 'object' && method.toUpperCase() === 'POST')\n {\n let entries = Object.entries(body);\n body = entries.map((kvp) => `${encodeURIComponent(kvp[0])}=${encodeURIComponent(kvp[1])}`).join('&');\n }\n xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');\n xhr.send(body);\n }\n });\n }\n\n/** Reformat an error (rejection) message from utils::justFetch, so that debugging code \n * can include (for example) a text-rendered version of the remote 404 page.\n *\n * @param {object} error The rejection from justFetch()\n * @returns {string} An error message, formatted with ANSI color when the output\n * is a terminal, suitable for writing directly to stdout. If\n * the response included html content (eg a 404 page), it is \n * rendered to text in this string.\n */\nexports.justFetchPrettyError = function utils$$justFetchPrettyError(error) {\n const chalk = new requireNative('chalk').constructor({enabled: (__webpack_require__(/*! dcp/utils */ \"./src/utils/index.js\").useChalk)});\n var message, headers={};\n\n if (!error.request || !error.request.status)\n return error;\n\n error.request.getAllResponseHeaders().replace(/\\r/g,'').split('\\n').forEach(function(line) {\n var colon = line.indexOf(': ')\n headers[line.slice(0,colon)] = line.slice(colon+2)\n })\n\n message = `HTTP Status: ${error.request.status} for ${error.request.method} ${error.request.location}`\n\n switch(headers['content-type'].replace(/;.*$/, '')) {\n case 'text/plain':\n message += '\\n' + chalk.grey(error.request.responseText)\n break;\n case 'text/html': {\n let html = error.request.responseText;\n\n html = html.replace(/\\n<a/gi, ' <a'); /* html-to-text bug, affects google 301s /wg jun 2020 */\n message += chalk.grey((__webpack_require__(/*! html-to-text */ \"./node_modules/html-to-text/index.js\").
|
|
4989
|
+
eval("/* provided dependency */ var process = __webpack_require__(/*! ./node_modules/process/browser.js */ \"./node_modules/process/browser.js\");\n/**\n * @file just-fetch.js\n * @author Ryan Rossiter <ryan@kingsds.network>\n * @date June 2020\n *\n * Cross-platform method for performing an HTTP request.\n */\n\n\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 { DcpURL } = __webpack_require__(/*! dcp/common/dcp-url */ \"./src/common/dcp-url.js\");\nconst kvin = __webpack_require__(/*! kvin */ \"./node_modules/kvin/kvin.js\");\n\n /**\n * Make a request, as a promise, resolves with a string or json object\n *\n * @param {string | object} url - path of file to be fetched; if object must be instanceof URL or DcpURL\n * @param {string} [type = \"string\"] - expected type of the response\n * @param {string} [method = \"GET\"] - type of request to make\n * @param {boolean} [bustCache = false] - whether to add cache poison to the end of the url\n * @param {any} [body] - what to send as the body of the message. Objects are turned into into \n * standard CGI key=value encoding when the method is 'POST'\n * @returns {Promise} - resolves with response (json if type=\"JSON\", string otherwise), rejects on fail\n */\nexports.justFetch = function justFetch(url, type = 'string', method = 'GET', bustCache = false, body = undefined) {\n return new Promise((resolve, reject) => {\n let deeperErrorStack = new Error().stack;\n deeperErrorStack = deeperErrorStack.substring(deeperErrorStack.indexOf('\\n') + 1);\n \n if (typeof url === 'object' && typeof url.href !== 'undefined')\n {\n assert(url.protocol !== 'data:');\n url = url.href;\n }\n\n if (bustCache)\n {\n let bustParam = ((typeof document !== 'undefined' && document.head && document.head.getAttribute('version')) || Date.now()); /* cache buster */\n\n url = new URL(url);\n url.search += (url.search ? '&' : '') + encodeURI(bustParam);\n url = url.href;\n }\n /* url now guaranteed to be a string */\n \n const xhr = new XMLHttpRequest();\n xhr.onloadend = function Protocol$$justFetch$onloadend() {\n try {\n delete xhr.onloadend;\n\n if (xhr.status >= 200 && xhr.status < 300) {\n if (xhr.getResponseHeader('content-type') && type === 'string') {\n type = xhr.getResponseHeader('content-type').split(';')[0];\n }\n \n let data = xhr.responseText;\n if (type === 'JSON' || type === 'application/json') {\n data = JSON.parse(data);\n }\n\n if (type === 'application/x-kvin' ) {\n data = kvin.deserialize(data);\n }\n \n resolve(data);\n } else {\n function makeFetchError()\n {\n if (xhr.status)\n return new Error(`HTTP Error ${xhr.status} fetching ${url}`);\n\n /* NodeJS via xmlhttprequest-ssl leaves an Error duck */\n if (xhr.statusText && xhr.statusText.code && xhr.statusText.message) {\n let error = new Error(xhr.statusText.message);\n let stack = error.stack;\n\n Object.assign(error, xhr.statusText);\n if (xhr.statusText.stack)\n error.stack = xhr.statusText.stack.split('\\n').slice(1).join('\\n') + '---\\n' + stack;\n else\n error.stack = stack;\n return error;\n }\n\n return new Error(`Network error fetching ${url}`);\n }\n\n const error = makeFetchError();\n \n error.request = Object.assign({}, xhr); /* Shallow clone so that console.log will work in node */\n error.request.method = method;\n error.request.location = url;\n error.stack += '\\n' + deeperErrorStack;\n if (xhr.status)\n error.code = 'HTTP_' + xhr.status;\n\n throw error;\n }\n } catch (e) {\n reject(e);\n }\n };\n\n xhr.open(method, url);\n if (!body)\n xhr.send();\n else\n {\n if (typeof body === 'object' && method.toUpperCase() === 'POST')\n {\n let entries = Object.entries(body);\n body = entries.map((kvp) => `${encodeURIComponent(kvp[0])}=${encodeURIComponent(kvp[1])}`).join('&');\n }\n xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');\n xhr.send(body);\n }\n });\n }\n\n/** Reformat an error (rejection) message from utils::justFetch, so that debugging code \n * can include (for example) a text-rendered version of the remote 404 page.\n *\n * @param {object} error The rejection from justFetch()\n * @returns {string} An error message, formatted with ANSI color when the output\n * is a terminal, suitable for writing directly to stdout. If\n * the response included html content (eg a 404 page), it is \n * rendered to text in this string.\n */\nexports.justFetchPrettyError = function utils$$justFetchPrettyError(error) {\n const chalk = new requireNative('chalk').constructor({enabled: (__webpack_require__(/*! dcp/utils */ \"./src/utils/index.js\").useChalk)});\n var message, headers={};\n\n if (!error.request || !error.request.status)\n return error;\n\n error.request.getAllResponseHeaders().replace(/\\r/g,'').split('\\n').forEach(function(line) {\n var colon = line.indexOf(': ')\n headers[line.slice(0,colon)] = line.slice(colon+2)\n })\n\n message = `HTTP Status: ${error.request.status} for ${error.request.method} ${error.request.location}`\n\n switch(headers['content-type'].replace(/;.*$/, '')) {\n case 'text/plain':\n message += '\\n' + chalk.grey(error.request.responseText)\n break;\n case 'text/html': {\n let html = error.request.responseText;\n\n html = html.replace(/\\n<a/gi, ' <a'); /* html-to-text bug, affects google 301s /wg jun 2020 */\n message += chalk.grey((__webpack_require__(/*! html-to-text */ \"./node_modules/html-to-text/index.js\").convert)(html, {\n wordwrap: parseInt(process.env.COLUMNS, 10) || 80,\n hideLinkHrefIfSameAsText: true,\n format: {\n heading: function (elem, fn, options) {\n var h = fn(elem.children, options);\n return '\\n====\\n' + chalk.yellow(chalk.bold(h.toUpperCase())) + '\\n====\\n';\n }\n }\n }));\n break;\n }\n }\n\n return message;\n} \n\n\n//# sourceURL=webpack://dcp/./src/utils/just-fetch.js?");
|
|
4906
4990
|
|
|
4907
4991
|
/***/ }),
|
|
4908
4992
|
|
|
@@ -4985,7 +5069,7 @@ eval("/* provided dependency */ var process = __webpack_require__(/*! ./node_mod
|
|
|
4985
5069
|
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
|
4986
5070
|
|
|
4987
5071
|
"use strict";
|
|
4988
|
-
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\");\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\n */\nexports.createTempThing = function createTempThing(createThingFn, basenamePattern, extension, tempDir)\n{\n const generateEntropy = requireNative('nanoid').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 process.off('dcpExit', removeFn);\n if (!process.env.DCP_DEBUG_TMP_FILES && !removed)\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 fs.rmdirSync(filename, { recursive: true, maxRetries: 20 }); /* maxRetries might need rimraf? rtfm later /wg aug 2022 */\n else\n throw error;\n }\n }\n catch(error)\n {\n console.warn(`Warning: unable to clean up '${filename}'`, error.message);\n }\n }\n }\n \n if (os.platform() !== 'win32')\n process.on('dcpExit', removeFn); /* Pipe namespace automatically cleaned up on win32 */\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 *\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) {\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);\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 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 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 socket.on('close', removeFn);\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?");
|
|
5072
|
+
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\");\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 = requireNative('nanoid').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 *\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) {\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);\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?");
|
|
4989
5073
|
|
|
4990
5074
|
/***/ }),
|
|
4991
5075
|
|
|
@@ -5389,6 +5473,17 @@ eval("/* (ignored) */\n\n//# sourceURL=webpack://dcp/util_(ignored)?");
|
|
|
5389
5473
|
|
|
5390
5474
|
/***/ }),
|
|
5391
5475
|
|
|
5476
|
+
/***/ "./node_modules/@selderee/plugin-htmlparser2/lib/hp2-builder.cjs":
|
|
5477
|
+
/*!***********************************************************************!*\
|
|
5478
|
+
!*** ./node_modules/@selderee/plugin-htmlparser2/lib/hp2-builder.cjs ***!
|
|
5479
|
+
\***********************************************************************/
|
|
5480
|
+
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
|
5481
|
+
|
|
5482
|
+
"use strict";
|
|
5483
|
+
eval("\n\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\n\nvar domhandler = __webpack_require__(/*! domhandler */ \"./node_modules/@selderee/plugin-htmlparser2/node_modules/domhandler/lib/index.js\");\nvar selderee = __webpack_require__(/*! selderee */ \"./node_modules/selderee/lib/selderee.cjs\");\n\n/**\r\n * A {@link BuilderFunction} implementation.\r\n *\r\n * Creates a function (in a {@link Picker} wrapper) that can run\r\n * the decision tree against `htmlparser2` `Element` nodes.\r\n *\r\n * @typeParam V - the type of values associated with selectors.\r\n *\r\n * @param nodes - nodes ({@link DecisionTreeNode})\r\n * from the root level of the decision tree.\r\n *\r\n * @returns a {@link Picker} object.\r\n */\r\nfunction hp2Builder(nodes) {\r\n return new selderee.Picker(handleArray(nodes));\r\n}\r\n// ==============================================\r\nfunction handleArray(nodes) {\r\n const matchers = nodes.map(handleNode);\r\n return (el, ...tail) => flatMap(matchers, m => m(el, ...tail));\r\n}\r\nfunction handleNode(node) {\r\n switch (node.type) {\r\n case 'terminal': {\r\n const result = [node.valueContainer];\r\n return (el, ...tail) => result;\r\n }\r\n case 'tagName':\r\n return handleTagName(node);\r\n case 'attrValue':\r\n return handleAttrValueName(node);\r\n case 'attrPresence':\r\n return handleAttrPresenceName(node);\r\n case 'pushElement':\r\n return handlePushElementNode(node);\r\n case 'popElement':\r\n return handlePopElementNode(node);\r\n }\r\n}\r\nfunction handleTagName(node) {\r\n const variants = {};\r\n for (const variant of node.variants) {\r\n variants[variant.value] = handleArray(variant.cont);\r\n }\r\n return (el, ...tail) => {\r\n const continuation = variants[el.name];\r\n return (continuation) ? continuation(el, ...tail) : [];\r\n };\r\n}\r\nfunction handleAttrPresenceName(node) {\r\n const attrName = node.name;\r\n const continuation = handleArray(node.cont);\r\n return (el, ...tail) => (Object.prototype.hasOwnProperty.call(el.attribs, attrName))\r\n ? continuation(el, ...tail)\r\n : [];\r\n}\r\nfunction handleAttrValueName(node) {\r\n const callbacks = [];\r\n for (const matcher of node.matchers) {\r\n const predicate = matcher.predicate;\r\n const continuation = handleArray(matcher.cont);\r\n callbacks.push((attr, el, ...tail) => (predicate(attr) ? continuation(el, ...tail) : []));\r\n }\r\n const attrName = node.name;\r\n return (el, ...tail) => {\r\n const attr = el.attribs[attrName];\r\n return (attr || attr === '')\r\n ? flatMap(callbacks, cb => cb(attr, el, ...tail))\r\n : [];\r\n };\r\n}\r\nfunction handlePushElementNode(node) {\r\n const continuation = handleArray(node.cont);\r\n const leftElementGetter = (node.combinator === '+')\r\n ? getPrecedingElement\r\n : getParentElement;\r\n return (el, ...tail) => {\r\n const next = leftElementGetter(el);\r\n if (next === null) {\r\n return [];\r\n }\r\n return continuation(next, el, ...tail);\r\n };\r\n}\r\nconst getPrecedingElement = (el) => {\r\n const prev = el.prev;\r\n if (prev === null) {\r\n return null;\r\n }\r\n return (domhandler.isTag(prev)) ? prev : getPrecedingElement(prev);\r\n};\r\nconst getParentElement = (el) => {\r\n const parent = el.parent;\r\n return (parent && domhandler.isTag(parent)) ? parent : null;\r\n};\r\nfunction handlePopElementNode(node) {\r\n const continuation = handleArray(node.cont);\r\n return (el, next, ...tail) => continuation(next, ...tail);\r\n}\r\n// Can be removed after transition to Node 12.\r\nfunction flatMap(items, mapper) {\r\n return [].concat(...amap(items, mapper));\r\n}\r\nfunction amap(items, mapper) {\r\n const len = items.length;\r\n const res = new Array(len);\r\n for (let i = 0; i < len; i++) {\r\n res[i] = mapper(items[i]);\r\n }\r\n return res;\r\n}\n\nexports.hp2Builder = hp2Builder;\n\n\n//# sourceURL=webpack://dcp/./node_modules/@selderee/plugin-htmlparser2/lib/hp2-builder.cjs?");
|
|
5484
|
+
|
|
5485
|
+
/***/ }),
|
|
5486
|
+
|
|
5392
5487
|
/***/ "./node_modules/available-typed-arrays/index.js":
|
|
5393
5488
|
/*!******************************************************!*\
|
|
5394
5489
|
!*** ./node_modules/available-typed-arrays/index.js ***!
|
|
@@ -5620,6 +5715,28 @@ eval("\n\nvar GetIntrinsic = __webpack_require__(/*! get-intrinsic */ \"./node_m
|
|
|
5620
5715
|
|
|
5621
5716
|
/***/ }),
|
|
5622
5717
|
|
|
5718
|
+
/***/ "./node_modules/parseley/lib/parseley.cjs":
|
|
5719
|
+
/*!************************************************!*\
|
|
5720
|
+
!*** ./node_modules/parseley/lib/parseley.cjs ***!
|
|
5721
|
+
\************************************************/
|
|
5722
|
+
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
|
5723
|
+
|
|
5724
|
+
"use strict";
|
|
5725
|
+
eval("\n\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\n\nvar nearley = __webpack_require__(/*! nearley */ \"./node_modules/nearley/lib/nearley.js\");\nvar moo = __webpack_require__(/*! moo */ \"./node_modules/moo/moo.js\");\n\nfunction _interopNamespace(e) {\n if (e && e.__esModule) return e;\n var n = Object.create(null);\n if (e) {\n Object.keys(e).forEach(function (k) {\n if (k !== 'default') {\n var d = Object.getOwnPropertyDescriptor(e, k);\n Object.defineProperty(n, k, d.get ? d : {\n enumerable: true,\n get: function () {\n return e[k];\n }\n });\n }\n });\n }\n n['default'] = e;\n return Object.freeze(n);\n}\n\nvar moo__namespace = /*#__PURE__*/_interopNamespace(moo);\n\n// Generated automatically by nearley, version 2.20.1\r\n// http://github.com/Hardmath123/nearley\r\n// Bypasses TS6133. Allow declared but unused functions.\r\n// @ts-ignore\r\nfunction id(d) { return d[0]; }\r\nconst lexer = moo__namespace.compile({\r\n ws: { match: /[ \\t\\r\\n\\f]+/, lineBreaks: true },\r\n idn: { match: /[a-zA-Z_-][a-zA-Z0-9_-]*/ },\r\n hashToken: { match: /#[a-zA-Z0-9_-]+/, value: (s) => s.slice(1) },\r\n str1: { match: /'(?:\\\\['\\\\]|[^\\n'\\\\])*'/, value: (s) => s.slice(1, -1) },\r\n str2: { match: /\"(?:\\\\[\"\\\\]|[^\\n\"\\\\])*\"/, value: (s) => s.slice(1, -1) },\r\n asterisk: '*',\r\n fullstop: '.',\r\n comma: ',',\r\n lbr: '[',\r\n rbr: ']',\r\n eq: '=',\r\n gt: '>',\r\n vbar: '|',\r\n plus: '+',\r\n tilde: '~',\r\n caret: '^',\r\n dollar: '$',\r\n //colon: ':',\r\n //lpar: '(',\r\n //rpar: ')',\r\n});\r\nfunction firstTokenValue(tokens) {\r\n return tokens[0].value;\r\n}\r\nfunction second(tokens) {\r\n return tokens[1];\r\n}\r\nfunction sumSpec([a0, a1, a2], [b0, b1, b2]) {\r\n return [a0 + b0, a1 + b1, a2 + b2];\r\n}\r\nconst grammar = {\r\n Lexer: lexer,\r\n ParserRules: [\r\n { \"name\": \"main\", \"symbols\": [\"_\", \"listSelector\", \"_\"], \"postprocess\": second },\r\n { \"name\": \"mainNoList\", \"symbols\": [\"_\", \"complexSelector\", \"_\"], \"postprocess\": second },\r\n { \"name\": \"listSelector\", \"symbols\": [\"complexSelector\"], \"postprocess\": ([next]) => ({ type: 'list', list: [next] }) },\r\n { \"name\": \"listSelector\", \"symbols\": [\"listSelector\", \"_\", (lexer.has(\"comma\") ? { type: \"comma\" } : comma), \"_\", \"complexSelector\"], \"postprocess\": ([acc, , , , next]) => ({ type: 'list', list: [...acc.list, next] }) },\r\n { \"name\": \"complexSelector\", \"symbols\": [\"compoundSelector\"], \"postprocess\": id },\r\n { \"name\": \"complexSelector\", \"symbols\": [\"complexSelector\", \"__\", \"compoundSelector\"], \"postprocess\": ([left, , right]) => ({\r\n type: 'compound',\r\n list: [...right.list, { type: 'combinator', combinator: ' ', left: left, specificity: left.specificity }],\r\n specificity: sumSpec(left.specificity, right.specificity)\r\n }) },\r\n { \"name\": \"complexSelector\", \"symbols\": [\"complexSelector\", \"_\", \"combinator\", \"_\", \"compoundSelector\"], \"postprocess\": ([left, , c, , right]) => ({\r\n type: 'compound',\r\n list: [...right.list, { type: 'combinator', combinator: c, left: left, specificity: left.specificity }],\r\n specificity: sumSpec(left.specificity, right.specificity)\r\n }) },\r\n { \"name\": \"combinator\", \"symbols\": [(lexer.has(\"gt\") ? { type: \"gt\" } : gt)], \"postprocess\": () => '>' },\r\n { \"name\": \"combinator\", \"symbols\": [(lexer.has(\"plus\") ? { type: \"plus\" } : plus)], \"postprocess\": () => '+' },\r\n { \"name\": \"combinator\", \"symbols\": [(lexer.has(\"tilde\") ? { type: \"tilde\" } : tilde)], \"postprocess\": () => '~' },\r\n { \"name\": \"combinator\", \"symbols\": [(lexer.has(\"vbar\") ? { type: \"vbar\" } : vbar), (lexer.has(\"vbar\") ? { type: \"vbar\" } : vbar)], \"postprocess\": () => '||' },\r\n { \"name\": \"compoundSelector\", \"symbols\": [\"typeSelector\"], \"postprocess\": ([next]) => ({\r\n type: 'compound',\r\n list: [next],\r\n specificity: next.specificity\r\n }) },\r\n { \"name\": \"compoundSelector\", \"symbols\": [\"subclassSelector\"], \"postprocess\": ([next]) => ({\r\n type: 'compound',\r\n list: [next],\r\n specificity: next.specificity\r\n }) },\r\n { \"name\": \"compoundSelector\", \"symbols\": [\"compoundSelector\", \"subclassSelector\"], \"postprocess\": ([acc, next]) => ({\r\n type: 'compound',\r\n list: [...acc.list, next],\r\n specificity: sumSpec(acc.specificity, next.specificity)\r\n }) },\r\n { \"name\": \"subclassSelector\", \"symbols\": [\"idSelector\"], \"postprocess\": id },\r\n { \"name\": \"subclassSelector\", \"symbols\": [\"classSelector\"], \"postprocess\": id },\r\n { \"name\": \"subclassSelector\", \"symbols\": [\"attrSelector\"], \"postprocess\": id },\r\n { \"name\": \"attrSelector\", \"symbols\": [\"attrPresenceSelector\"], \"postprocess\": id },\r\n { \"name\": \"attrSelector\", \"symbols\": [\"attrValueSelector\"], \"postprocess\": id },\r\n { \"name\": \"typeSelector\", \"symbols\": [\"tagSelector\"], \"postprocess\": id },\r\n { \"name\": \"typeSelector\", \"symbols\": [\"uniSelector\"], \"postprocess\": id },\r\n { \"name\": \"attrPresenceSelector\", \"symbols\": [(lexer.has(\"lbr\") ? { type: \"lbr\" } : lbr), \"_\", \"wqname\", \"_\", (lexer.has(\"rbr\") ? { type: \"rbr\" } : rbr)], \"postprocess\": ([, , wqname]) => ({\r\n type: 'attrPresence',\r\n name: wqname.name,\r\n namespace: wqname.namespace,\r\n specificity: [0, 1, 0]\r\n })\r\n },\r\n { \"name\": \"attrValueSelector\", \"symbols\": [(lexer.has(\"lbr\") ? { type: \"lbr\" } : lbr), \"_\", \"wqname\", \"_\", \"attrMatcher\", \"_\", \"attrValue\", \"_\", (lexer.has(\"rbr\") ? { type: \"rbr\" } : rbr)], \"postprocess\": ([, , wqname, , matcher, , v]) => ({\r\n type: 'attrValue',\r\n name: wqname.name,\r\n namespace: wqname.namespace,\r\n matcher: matcher,\r\n value: v.value,\r\n modifier: v.modifier,\r\n specificity: [0, 1, 0]\r\n })\r\n },\r\n { \"name\": \"attrMatcher\", \"symbols\": [(lexer.has(\"eq\") ? { type: \"eq\" } : eq)], \"postprocess\": () => '=' },\r\n { \"name\": \"attrMatcher\", \"symbols\": [(lexer.has(\"tilde\") ? { type: \"tilde\" } : tilde), (lexer.has(\"eq\") ? { type: \"eq\" } : eq)], \"postprocess\": () => '~=' },\r\n { \"name\": \"attrMatcher\", \"symbols\": [(lexer.has(\"vbar\") ? { type: \"vbar\" } : vbar), (lexer.has(\"eq\") ? { type: \"eq\" } : eq)], \"postprocess\": () => '|=' },\r\n { \"name\": \"attrMatcher\", \"symbols\": [(lexer.has(\"caret\") ? { type: \"caret\" } : caret), (lexer.has(\"eq\") ? { type: \"eq\" } : eq)], \"postprocess\": () => '^=' },\r\n { \"name\": \"attrMatcher\", \"symbols\": [(lexer.has(\"dollar\") ? { type: \"dollar\" } : dollar), (lexer.has(\"eq\") ? { type: \"eq\" } : eq)], \"postprocess\": () => '$=' },\r\n { \"name\": \"attrMatcher\", \"symbols\": [(lexer.has(\"asterisk\") ? { type: \"asterisk\" } : asterisk), (lexer.has(\"eq\") ? { type: \"eq\" } : eq)], \"postprocess\": () => '*=' },\r\n { \"name\": \"attrValue\", \"symbols\": [\"str\"], \"postprocess\": ([v]) => ({ value: v, modifier: null }) },\r\n { \"name\": \"attrValue\", \"symbols\": [\"idn\"], \"postprocess\": ([v]) => ({ value: v, modifier: null }) },\r\n { \"name\": \"attrValue\", \"symbols\": [\"str\", \"_\", \"attrModifier\"], \"postprocess\": ([v, , mod]) => ({ value: v, modifier: mod }) },\r\n { \"name\": \"attrValue\", \"symbols\": [\"idn\", \"__\", \"attrModifier\"], \"postprocess\": ([v, , mod]) => ({ value: v, modifier: mod }) },\r\n { \"name\": \"attrModifier\", \"symbols\": [{ \"literal\": \"i\" }], \"postprocess\": () => 'i' },\r\n { \"name\": \"attrModifier\", \"symbols\": [{ \"literal\": \"I\" }], \"postprocess\": () => 'i' },\r\n { \"name\": \"attrModifier\", \"symbols\": [{ \"literal\": \"s\" }], \"postprocess\": () => 's' },\r\n { \"name\": \"attrModifier\", \"symbols\": [{ \"literal\": \"S\" }], \"postprocess\": () => 's' },\r\n { \"name\": \"idSelector\", \"symbols\": [(lexer.has(\"hashToken\") ? { type: \"hashToken\" } : hashToken)], \"postprocess\": ([{ value: name }]) => ({ type: 'id', name: name, specificity: [1, 0, 0] }) },\r\n { \"name\": \"classSelector\", \"symbols\": [(lexer.has(\"fullstop\") ? { type: \"fullstop\" } : fullstop), \"idn\"], \"postprocess\": ([, name]) => ({ type: 'class', name: name, specificity: [0, 1, 0] }) },\r\n { \"name\": \"tagSelector\", \"symbols\": [\"wqname\"], \"postprocess\": ([wqname]) => ({\r\n type: 'tag',\r\n name: wqname.name,\r\n namespace: wqname.namespace,\r\n specificity: [0, 0, 1]\r\n })\r\n },\r\n { \"name\": \"uniSelector\", \"symbols\": [(lexer.has(\"asterisk\") ? { type: \"asterisk\" } : asterisk)], \"postprocess\": () => ({ type: 'universal', namespace: null, specificity: [0, 0, 0] }) },\r\n { \"name\": \"uniSelector\", \"symbols\": [\"ns\", (lexer.has(\"asterisk\") ? { type: \"asterisk\" } : asterisk)], \"postprocess\": ([ns]) => ({ type: 'universal', namespace: ns, specificity: [0, 0, 0] }) },\r\n { \"name\": \"wqname\", \"symbols\": [\"idn\"], \"postprocess\": ([name]) => ({ name: name, namespace: null }) },\r\n { \"name\": \"wqname\", \"symbols\": [\"ns\", \"idn\"], \"postprocess\": ([ns, name]) => ({ name: name, namespace: ns }) },\r\n { \"name\": \"ns\", \"symbols\": [(lexer.has(\"vbar\") ? { type: \"vbar\" } : vbar)], \"postprocess\": () => '' },\r\n { \"name\": \"ns\", \"symbols\": [\"idn\", (lexer.has(\"vbar\") ? { type: \"vbar\" } : vbar)], \"postprocess\": id },\r\n { \"name\": \"str\", \"symbols\": [(lexer.has(\"str1\") ? { type: \"str1\" } : str1)], \"postprocess\": firstTokenValue },\r\n { \"name\": \"str\", \"symbols\": [(lexer.has(\"str2\") ? { type: \"str2\" } : str2)], \"postprocess\": firstTokenValue },\r\n { \"name\": \"idn\", \"symbols\": [(lexer.has(\"idn\") ? { type: \"idn\" } : idn)], \"postprocess\": firstTokenValue },\r\n { \"name\": \"_$ebnf$1\", \"symbols\": [(lexer.has(\"ws\") ? { type: \"ws\" } : ws)], \"postprocess\": id },\r\n { \"name\": \"_$ebnf$1\", \"symbols\": [], \"postprocess\": () => null },\r\n { \"name\": \"_\", \"symbols\": [\"_$ebnf$1\"], \"postprocess\": () => null },\r\n { \"name\": \"__\", \"symbols\": [(lexer.has(\"ws\") ? { type: \"ws\" } : ws)], \"postprocess\": () => null }\r\n ],\r\n ParserStart: \"main\",\r\n};\n\nvar ast = /*#__PURE__*/Object.freeze({\n __proto__: null\n});\n\n// Passing the start argument to a parser or grammar constructor\r\n// doesn't seem to work as expected.\r\nconst compiledRulesNoList = { ...grammar, ParserStart: 'mainNoList' };\r\n/**\r\n * Parse a CSS selector string.\r\n *\r\n * This function supports comma-separated selector lists\r\n * and always returns an AST starting from a node of type `list`.\r\n *\r\n * @param str - CSS selector string (can contain commas).\r\n */\r\nfunction parse(str) {\r\n return _parse(grammar, str);\r\n}\r\n/**\r\n * Parse a CSS selector string.\r\n *\r\n * This function does not support comma-separated selector lists\r\n * and always returns an AST starting from a node of type `compound`.\r\n *\r\n * @param str - CSS selector string (no commas).\r\n */\r\nfunction parse1(str) {\r\n return _parse(compiledRulesNoList, str);\r\n}\r\nfunction _parse(compiledRules1, str) {\r\n const parser = new nearley.Parser(nearley.Grammar.fromCompiled(compiledRules1));\r\n parser.feed(str);\r\n if (parser.results.length === 0) {\r\n throw new Error('Failed to parse - input string might be incomplete.');\r\n }\r\n return parser.results[0];\r\n}\r\n/**\r\n * Convert a selector AST back to a string representation.\r\n *\r\n * Note: formatting is not preserved in the AST.\r\n *\r\n * @param selector - A selector AST object.\r\n */\r\nfunction serialize(selector) {\r\n if (!selector.type) {\r\n throw new Error('This is not an AST node.');\r\n }\r\n switch (selector.type) {\r\n case 'universal':\r\n return _serNs(selector.namespace) + '*';\r\n case 'tag':\r\n return _serNs(selector.namespace) + selector.name;\r\n case 'class':\r\n return '.' + selector.name;\r\n case 'id':\r\n return '#' + selector.name;\r\n case 'attrPresence':\r\n return `[${_serNs(selector.namespace)}${selector.name}]`;\r\n case 'attrValue':\r\n return `[${_serNs(selector.namespace)}${selector.name}${selector.matcher}${_serStr(selector.value)}${(selector.modifier ? selector.modifier : '')}]`;\r\n case 'combinator':\r\n return serialize(selector.left) + selector.combinator;\r\n case 'compound':\r\n return selector.list.reduce((acc, node) => {\r\n if (node.type === 'combinator') {\r\n return serialize(node) + acc;\r\n }\r\n else {\r\n return acc + serialize(node);\r\n }\r\n }, '');\r\n case 'list':\r\n return selector.list.map(serialize).join(',');\r\n }\r\n}\r\nfunction _serNs(ns) {\r\n return (ns || ns === '')\r\n ? ns + '|'\r\n : '';\r\n}\r\nfunction _serStr(str) {\r\n if (str.indexOf('\"') === -1) {\r\n return `\"${str}\"`;\r\n }\r\n else if (str.indexOf(\"'\") === -1) {\r\n return `'${str}'`;\r\n }\r\n else {\r\n return `\"${str.replace('\"', '\\\\\"')}\"`;\r\n }\r\n}\r\n/**\r\n * Modifies the given AST **in place** to have all internal arrays\r\n * in a stable order. Returns the AST.\r\n *\r\n * Intended for consitent processing and normalized `serialize()` output.\r\n *\r\n * @param selector - A selector AST object.\r\n */\r\nfunction normalize(selector) {\r\n if (!selector.type) {\r\n throw new Error('This is not an AST node.');\r\n }\r\n switch (selector.type) {\r\n case 'compound': {\r\n selector.list.forEach(normalize);\r\n selector.list.sort((a, b) => _compareArrays(_getSelectorPriority(a), _getSelectorPriority(b)));\r\n break;\r\n }\r\n case 'combinator': {\r\n normalize(selector.left);\r\n break;\r\n }\r\n case 'list': {\r\n selector.list.forEach(normalize);\r\n selector.list.sort((a, b) => (serialize(a) < serialize(b)) ? -1 : 1);\r\n break;\r\n }\r\n }\r\n return selector;\r\n}\r\nfunction _getSelectorPriority(selector) {\r\n switch (selector.type) {\r\n case 'universal':\r\n return [1];\r\n case 'tag':\r\n return [1];\r\n case 'id':\r\n return [2];\r\n case 'class':\r\n return [3, selector.name];\r\n case 'attrPresence':\r\n return [4, serialize(selector)];\r\n case 'attrValue':\r\n return [5, serialize(selector)];\r\n case 'combinator':\r\n return [15, serialize(selector)];\r\n }\r\n}\r\n/**\r\n * Compare selectors based on their specificity.\r\n *\r\n * Usable as a comparator for sorting.\r\n *\r\n * @param a - First selector.\r\n * @param b - Second selector.\r\n */\r\nfunction compareSelectors(a, b) {\r\n return _compareArrays(a.specificity, b.specificity);\r\n}\r\n/**\r\n * Compare specificity values without reducing them\r\n * as arbitrary base numbers.\r\n *\r\n * Usable as a comparator for sorting.\r\n *\r\n * @param a - First specificity value.\r\n * @param b - Second specificity value.\r\n */\r\nfunction compareSpecificity(a, b) {\r\n return _compareArrays(a, b);\r\n}\r\nfunction _compareArrays(a, b) {\r\n if (!Array.isArray(a) || !Array.isArray(b)) {\r\n throw new Error('Arguments must be arrays.');\r\n }\r\n const shorter = (a.length < b.length) ? a.length : b.length;\r\n for (let i = 0; i < shorter; i++) {\r\n if (a[i] === b[i]) {\r\n continue;\r\n }\r\n return (a[i] < b[i]) ? -1 : 1;\r\n }\r\n return a.length - b.length;\r\n}\n\nexports.Ast = ast;\nexports.compareSelectors = compareSelectors;\nexports.compareSpecificity = compareSpecificity;\nexports.normalize = normalize;\nexports.parse = parse;\nexports.parse1 = parse1;\nexports.serialize = serialize;\n\n\n//# sourceURL=webpack://dcp/./node_modules/parseley/lib/parseley.cjs?");
|
|
5726
|
+
|
|
5727
|
+
/***/ }),
|
|
5728
|
+
|
|
5729
|
+
/***/ "./node_modules/selderee/lib/selderee.cjs":
|
|
5730
|
+
/*!************************************************!*\
|
|
5731
|
+
!*** ./node_modules/selderee/lib/selderee.cjs ***!
|
|
5732
|
+
\************************************************/
|
|
5733
|
+
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
|
5734
|
+
|
|
5735
|
+
"use strict";
|
|
5736
|
+
eval("\n\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\n\nvar parseley = __webpack_require__(/*! parseley */ \"./node_modules/parseley/lib/parseley.cjs\");\n\nfunction _interopNamespace(e) {\n if (e && e.__esModule) return e;\n var n = Object.create(null);\n if (e) {\n Object.keys(e).forEach(function (k) {\n if (k !== 'default') {\n var d = Object.getOwnPropertyDescriptor(e, k);\n Object.defineProperty(n, k, d.get ? d : {\n enumerable: true,\n get: function () {\n return e[k];\n }\n });\n }\n });\n }\n n['default'] = e;\n return Object.freeze(n);\n}\n\nvar parseley__namespace = /*#__PURE__*/_interopNamespace(parseley);\n\nvar Ast = /*#__PURE__*/Object.freeze({\n __proto__: null\n});\n\nvar Types = /*#__PURE__*/Object.freeze({\n __proto__: null\n});\n\n/**\r\n * A {@link BuilderFunction} implementation.\r\n *\r\n * Produces a string representation of the tree\r\n * for testing and debug purposes.\r\n *\r\n * Only accepts `string` as the associated value type.\r\n * Map your input collection before creating a {@link DecisionTree}\r\n * if you want to use it with a different type -\r\n * the decision on how to stringify the value is up to you.\r\n *\r\n * @param nodes - nodes from the root level of the decision tree.\r\n * @returns the string representation of the tree.\r\n */\r\nconst treeify = (nodes) => '▽\\n' + treeifyArray(nodes, thinLines);\r\nconst thinLines = [['├─', '│ '], ['└─', ' ']];\r\nconst heavyLines = [['┠─', '┃ '], ['┖─', ' ']];\r\nconst doubleLines = [['╟─', '║ '], ['╙─', ' ']];\r\nfunction treeifyArray(nodes, tpl = heavyLines) {\r\n return prefixItems(tpl, nodes.map(n => treeifyNode(n)));\r\n}\r\nfunction treeifyNode(node) {\r\n switch (node.type) {\r\n case 'terminal': {\r\n const vctr = node.valueContainer;\r\n return `◁ #${vctr.index} ${JSON.stringify(vctr.specificity)} ${vctr.value}`;\r\n }\r\n case 'tagName':\r\n return `◻ Tag name\\n${treeifyArray(node.variants, doubleLines)}`;\r\n case 'attrValue':\r\n return `▣ Attr value: ${node.name}\\n${treeifyArray(node.matchers, doubleLines)}`;\r\n case 'attrPresence':\r\n return `◨ Attr presence: ${node.name}\\n${treeifyArray(node.cont)}`;\r\n case 'pushElement':\r\n return `◉ Push element: ${node.combinator}\\n${treeifyArray(node.cont, thinLines)}`;\r\n case 'popElement':\r\n return `◌ Pop element\\n${treeifyArray(node.cont, thinLines)}`;\r\n case 'variant':\r\n return `◇ = ${node.value}\\n${treeifyArray(node.cont)}`;\r\n case 'matcher':\r\n return `◈ ${node.matcher} \"${node.value}\"${node.modifier || ''}\\n${treeifyArray(node.cont)}`;\r\n }\r\n}\r\nfunction prefixItems(tpl, items) {\r\n return items\r\n .map((item, i, { length }) => prefixItem(tpl, item, i === length - 1))\r\n .join('\\n');\r\n}\r\nfunction prefixItem(tpl, item, tail = true) {\r\n const tpl1 = tpl[tail ? 1 : 0];\r\n return tpl1[0] + item.split('\\n').join('\\n' + tpl1[1]);\r\n}\n\nvar TreeifyBuilder = /*#__PURE__*/Object.freeze({\n __proto__: null,\n treeify: treeify\n});\n\n/**\r\n * CSS selectors decision tree.\r\n * Data structure that weaves similar selectors together\r\n * in order to minimize the number of checks required\r\n * to find the ones matching a given HTML element.\r\n *\r\n * Converted into a functioning implementation via plugins\r\n * tailored for specific DOM ASTs.\r\n *\r\n * @typeParam V - the type of values associated with selectors.\r\n */\r\nclass DecisionTree {\r\n /**\r\n * Create new DecisionTree object.\r\n *\r\n * @param input - an array containing all selectors\r\n * paired with associated values.\r\n *\r\n * @typeParam V - the type of values associated with selectors.\r\n */\r\n constructor(input) {\r\n this.branches = weave(toAstTerminalPairs(input));\r\n }\r\n /**\r\n * Turn this decision tree into a usable form.\r\n *\r\n * @typeParam R - return type defined by the builder function.\r\n *\r\n * @param builder - the builder function.\r\n *\r\n * @returns the decision tree in a form ready for use.\r\n */\r\n build(builder) {\r\n return builder(this.branches);\r\n }\r\n}\r\nfunction toAstTerminalPairs(array) {\r\n const len = array.length;\r\n const results = new Array(len);\r\n for (let i = 0; i < len; i++) {\r\n const [selectorString, val] = array[i];\r\n const ast = preprocess(parseley__namespace.parse1(selectorString));\r\n results[i] = {\r\n ast: ast,\r\n terminal: {\r\n type: 'terminal',\r\n valueContainer: { index: i, value: val, specificity: ast.specificity }\r\n }\r\n };\r\n }\r\n return results;\r\n}\r\nfunction preprocess(ast) {\r\n reduceSelectorVariants(ast);\r\n parseley__namespace.normalize(ast);\r\n return ast;\r\n}\r\nfunction reduceSelectorVariants(ast) {\r\n const newList = [];\r\n ast.list.forEach(sel => {\r\n switch (sel.type) {\r\n case 'class':\r\n newList.push({\r\n matcher: '~=',\r\n modifier: null,\r\n name: 'class',\r\n namespace: null,\r\n specificity: sel.specificity,\r\n type: 'attrValue',\r\n value: sel.name,\r\n });\r\n break;\r\n case 'id':\r\n newList.push({\r\n matcher: '=',\r\n modifier: null,\r\n name: 'id',\r\n namespace: null,\r\n specificity: sel.specificity,\r\n type: 'attrValue',\r\n value: sel.name,\r\n });\r\n break;\r\n case 'combinator':\r\n reduceSelectorVariants(sel.left);\r\n newList.push(sel);\r\n break;\r\n case 'universal':\r\n // skip it\r\n break;\r\n default:\r\n newList.push(sel);\r\n break;\r\n }\r\n });\r\n ast.list = newList;\r\n}\r\nfunction weave(items) {\r\n const branches = [];\r\n while (items.length) {\r\n const topKind = findTopKey(items, (sel) => true, getSelectorKind);\r\n const { matches, nonmatches, empty } = breakByKind(items, topKind);\r\n items = nonmatches;\r\n if (matches.length) {\r\n branches.push(branchOfKind(topKind, matches));\r\n }\r\n if (empty.length) {\r\n branches.push(...terminate(empty));\r\n }\r\n }\r\n return branches;\r\n}\r\nfunction terminate(items) {\r\n const results = [];\r\n for (const item of items) {\r\n const terminal = item.terminal;\r\n if (terminal.type === 'terminal') {\r\n results.push(terminal);\r\n }\r\n else { // popElement - lift contained terminals\r\n const { matches, rest } = partition(terminal.cont, (node) => node.type === 'terminal');\r\n matches.forEach((node) => results.push(node));\r\n if (rest.length) {\r\n terminal.cont = rest;\r\n results.push(terminal);\r\n }\r\n }\r\n }\r\n return results;\r\n}\r\nfunction breakByKind(items, selectedKind) {\r\n const matches = [];\r\n const nonmatches = [];\r\n const empty = [];\r\n for (const item of items) {\r\n const simpsels = item.ast.list;\r\n if (simpsels.length) {\r\n const isMatch = simpsels.some(node => getSelectorKind(node) === selectedKind);\r\n (isMatch ? matches : nonmatches).push(item);\r\n }\r\n else {\r\n empty.push(item);\r\n }\r\n }\r\n return { matches, nonmatches, empty };\r\n}\r\nfunction getSelectorKind(sel) {\r\n switch (sel.type) {\r\n case 'attrPresence':\r\n return `attrPresence ${sel.name}`;\r\n case 'attrValue':\r\n return `attrValue ${sel.name}`;\r\n case 'combinator':\r\n return `combinator ${sel.combinator}`;\r\n default:\r\n return sel.type;\r\n }\r\n}\r\nfunction branchOfKind(kind, items) {\r\n if (kind === 'tag') {\r\n return tagNameBranch(items);\r\n }\r\n if (kind.startsWith('attrValue ')) {\r\n return attrValueBranch(kind.substring(10), items);\r\n }\r\n if (kind.startsWith('attrPresence ')) {\r\n return attrPresenceBranch(kind.substring(13), items);\r\n }\r\n if (kind === 'combinator >') {\r\n return combinatorBranch('>', items);\r\n }\r\n if (kind === 'combinator +') {\r\n return combinatorBranch('+', items);\r\n }\r\n throw new Error(`Unsupported selector kind: ${kind}`);\r\n}\r\nfunction tagNameBranch(items) {\r\n const groups = spliceAndGroup(items, (x) => x.type === 'tag', (x) => x.name);\r\n const variants = Object.entries(groups).map(([name, group]) => ({\r\n type: 'variant',\r\n value: name,\r\n cont: weave(group.items)\r\n }));\r\n return {\r\n type: 'tagName',\r\n variants: variants\r\n };\r\n}\r\nfunction attrPresenceBranch(name, items) {\r\n for (const item of items) {\r\n spliceSimpleSelector(item, (x) => (x.type === 'attrPresence') && (x.name === name));\r\n }\r\n return {\r\n type: 'attrPresence',\r\n name: name,\r\n cont: weave(items)\r\n };\r\n}\r\nfunction attrValueBranch(name, items) {\r\n const groups = spliceAndGroup(items, (x) => (x.type === 'attrValue') && (x.name === name), (x) => `${x.matcher} ${x.modifier || ''} ${x.value}`);\r\n const matchers = [];\r\n for (const group of Object.values(groups)) {\r\n const sel = group.oneSimpleSelector;\r\n const predicate = getAttrPredicate(sel);\r\n const continuation = weave(group.items);\r\n matchers.push({\r\n type: 'matcher',\r\n matcher: sel.matcher,\r\n modifier: sel.modifier,\r\n value: sel.value,\r\n predicate: predicate,\r\n cont: continuation\r\n });\r\n }\r\n return {\r\n type: 'attrValue',\r\n name: name,\r\n matchers: matchers\r\n };\r\n}\r\nfunction getAttrPredicate(sel) {\r\n if (sel.modifier === 'i') {\r\n const expected = sel.value.toLowerCase();\r\n switch (sel.matcher) {\r\n case '=':\r\n return (actual) => expected === actual.toLowerCase();\r\n case '~=':\r\n return (actual) => actual.toLowerCase().split(/[ \\t]+/).includes(expected);\r\n case '^=':\r\n return (actual) => actual.toLowerCase().startsWith(expected);\r\n case '$=':\r\n return (actual) => actual.toLowerCase().endsWith(expected);\r\n case '*=':\r\n return (actual) => actual.toLowerCase().includes(expected);\r\n case '|=':\r\n return (actual) => {\r\n const lower = actual.toLowerCase();\r\n return (expected === lower) || (lower.startsWith(expected) && lower[expected.length] === '-');\r\n };\r\n }\r\n }\r\n else {\r\n const expected = sel.value;\r\n switch (sel.matcher) {\r\n case '=':\r\n return (actual) => expected === actual;\r\n case '~=':\r\n return (actual) => actual.split(/[ \\t]+/).includes(expected);\r\n case '^=':\r\n return (actual) => actual.startsWith(expected);\r\n case '$=':\r\n return (actual) => actual.endsWith(expected);\r\n case '*=':\r\n return (actual) => actual.includes(expected);\r\n case '|=':\r\n return (actual) => (expected === actual) || (actual.startsWith(expected) && actual[expected.length] === '-');\r\n }\r\n }\r\n}\r\nfunction combinatorBranch(combinator, items) {\r\n const groups = spliceAndGroup(items, (x) => (x.type === 'combinator') && (x.combinator === combinator), (x) => parseley__namespace.serialize(x.left));\r\n const leftItems = [];\r\n for (const group of Object.values(groups)) {\r\n const rightCont = weave(group.items);\r\n const leftAst = group.oneSimpleSelector.left;\r\n leftItems.push({\r\n ast: leftAst,\r\n terminal: { type: 'popElement', cont: rightCont }\r\n });\r\n }\r\n return {\r\n type: 'pushElement',\r\n combinator: combinator,\r\n cont: weave(leftItems)\r\n };\r\n}\r\nfunction spliceAndGroup(items, predicate, keyCallback) {\r\n const groups = {};\r\n while (items.length) {\r\n const bestKey = findTopKey(items, predicate, keyCallback);\r\n const bestKeyPredicate = (sel) => predicate(sel) && keyCallback(sel) === bestKey;\r\n const hasBestKeyPredicate = (item) => item.ast.list.some(bestKeyPredicate);\r\n const { matches, rest } = partition1(items, hasBestKeyPredicate);\r\n let oneSimpleSelector = null;\r\n for (const item of matches) {\r\n const splicedNode = spliceSimpleSelector(item, bestKeyPredicate);\r\n if (!oneSimpleSelector) {\r\n oneSimpleSelector = splicedNode;\r\n }\r\n }\r\n if (oneSimpleSelector == null) {\r\n throw new Error('No simple selector is found.');\r\n }\r\n groups[bestKey] = { oneSimpleSelector: oneSimpleSelector, items: matches };\r\n items = rest;\r\n }\r\n return groups;\r\n}\r\nfunction spliceSimpleSelector(item, predicate) {\r\n const simpsels = item.ast.list;\r\n const matches = new Array(simpsels.length);\r\n let firstIndex = -1;\r\n for (let i = simpsels.length; i-- > 0;) {\r\n if (predicate(simpsels[i])) {\r\n matches[i] = true;\r\n firstIndex = i;\r\n }\r\n }\r\n if (firstIndex == -1) {\r\n throw new Error(`Couldn't find the required simple selector.`);\r\n }\r\n const result = simpsels[firstIndex];\r\n item.ast.list = simpsels.filter((sel, i) => !matches[i]);\r\n return result;\r\n}\r\nfunction findTopKey(items, predicate, keyCallback) {\r\n const candidates = {};\r\n for (const item of items) {\r\n const candidates1 = {};\r\n for (const node of item.ast.list.filter(predicate)) {\r\n candidates1[keyCallback(node)] = true;\r\n }\r\n for (const key of Object.keys(candidates1)) {\r\n if (candidates[key]) {\r\n candidates[key]++;\r\n }\r\n else {\r\n candidates[key] = 1;\r\n }\r\n }\r\n }\r\n let topKind = '';\r\n let topCounter = 0;\r\n for (const entry of Object.entries(candidates)) {\r\n if (entry[1] > topCounter) {\r\n topKind = entry[0];\r\n topCounter = entry[1];\r\n }\r\n }\r\n return topKind;\r\n}\r\nfunction partition(src, predicate) {\r\n const matches = [];\r\n const rest = [];\r\n for (const x of src) {\r\n if (predicate(x)) {\r\n matches.push(x);\r\n }\r\n else {\r\n rest.push(x);\r\n }\r\n }\r\n return { matches, rest };\r\n}\r\nfunction partition1(src, predicate) {\r\n const matches = [];\r\n const rest = [];\r\n for (const x of src) {\r\n if (predicate(x)) {\r\n matches.push(x);\r\n }\r\n else {\r\n rest.push(x);\r\n }\r\n }\r\n return { matches, rest };\r\n}\n\n/**\r\n * Simple wrapper around the matcher function.\r\n * Recommended return type for builder plugins.\r\n *\r\n * @typeParam L - the type of HTML Element in the targeted DOM AST.\r\n * @typeParam V - the type of associated values.\r\n */\r\nclass Picker {\r\n /**\r\n * Create new Picker object.\r\n *\r\n * @typeParam L - the type of HTML Element in the targeted DOM AST.\r\n * @typeParam V - the type of associated values.\r\n *\r\n * @param f - the function that matches an element\r\n * and returns all associated values.\r\n */\r\n constructor(f) {\r\n this.f = f;\r\n }\r\n /**\r\n * Run the selectors decision tree against one HTML Element\r\n * and return all matched associated values\r\n * along with selector specificities.\r\n *\r\n * Client code then decides how to further process them\r\n * (sort, filter, etc).\r\n *\r\n * @param el - an HTML Element.\r\n *\r\n * @returns all associated values along with\r\n * selector specificities for all matched selectors.\r\n */\r\n pickAll(el) {\r\n return this.f(el);\r\n }\r\n /**\r\n * Run the selectors decision tree against one HTML Element\r\n * and choose the value from the most specific mached selector.\r\n *\r\n * @param el - an HTML Element.\r\n *\r\n * @param preferFirst - option to define which value to choose\r\n * when there are multiple matches with equal specificity.\r\n *\r\n * @returns the value from the most specific mached selector\r\n * or `null` if nothing matched.\r\n */\r\n pick1(el, preferFirst = false) {\r\n const results = this.f(el);\r\n const len = results.length;\r\n if (len === 0) {\r\n return null;\r\n }\r\n if (len === 1) {\r\n return results[0].value;\r\n }\r\n const comparator = (preferFirst)\r\n ? comparatorPreferFirst\r\n : comparatorPreferLast;\r\n let result = results[0];\r\n for (let i = 1; i < len; i++) {\r\n const next = results[i];\r\n if (comparator(result, next)) {\r\n result = next;\r\n }\r\n }\r\n return result.value;\r\n }\r\n}\r\nfunction comparatorPreferFirst(acc, next) {\r\n const diff = parseley.compareSpecificity(next.specificity, acc.specificity);\r\n return diff > 0 || (diff === 0 && next.index < acc.index);\r\n}\r\nfunction comparatorPreferLast(acc, next) {\r\n const diff = parseley.compareSpecificity(next.specificity, acc.specificity);\r\n return diff > 0 || (diff === 0 && next.index > acc.index);\r\n}\n\nexports.Ast = Ast;\nexports.DecisionTree = DecisionTree;\nexports.Picker = Picker;\nexports.Treeify = TreeifyBuilder;\nexports.Types = Types;\n\n\n//# sourceURL=webpack://dcp/./node_modules/selderee/lib/selderee.cjs?");
|
|
5737
|
+
|
|
5738
|
+
/***/ }),
|
|
5739
|
+
|
|
5623
5740
|
/***/ "./node_modules/socket.io-client/build/cjs/contrib/backo2.js":
|
|
5624
5741
|
/*!*******************************************************************!*\
|
|
5625
5742
|
!*** ./node_modules/socket.io-client/build/cjs/contrib/backo2.js ***!
|
|
@@ -5638,7 +5755,7 @@ eval("\n/**\n * Initialize backoff timer with `opts`.\n *\n * - `min` initial ti
|
|
|
5638
5755
|
/***/ (function(module, exports, __webpack_require__) {
|
|
5639
5756
|
|
|
5640
5757
|
"use strict";
|
|
5641
|
-
eval("\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports[\"default\"] = exports.connect = exports.io = exports.Socket = exports.Manager = exports.protocol = void 0;\nconst url_js_1 = __webpack_require__(/*! ./url.js */ \"./node_modules/socket.io-client/build/cjs/url.js\");\nconst manager_js_1 = __webpack_require__(/*! ./manager.js */ \"./node_modules/socket.io-client/build/cjs/manager.js\");\nObject.defineProperty(exports, \"Manager\", ({ enumerable: true, get: function () { return manager_js_1.Manager; } }));\nconst socket_js_1 = __webpack_require__(/*! ./socket.js */ \"./node_modules/socket.io-client/build/cjs/socket.js\");\nObject.defineProperty(exports, \"Socket\", ({ enumerable: true, get: function () { return socket_js_1.Socket; } }));\nconst debug_1 = __importDefault(__webpack_require__(/*! debug */ \"./node_modules/debug/src/browser.js\")); // debug()\nconst debug = debug_1.default(\"socket.io-client\"); // debug()\n/**\n * Managers cache.\n */\nconst cache = {};\nfunction lookup(uri, opts) {\n if (typeof uri === \"object\") {\n opts = uri;\n uri = undefined;\n }\n opts = opts || {};\n const parsed = url_js_1.url(uri, opts.path || \"/socket.io\");\n const source = parsed.source;\n const id = parsed.id;\n const path = parsed.path;\n const sameNamespace = cache[id] && path in cache[id][\"nsps\"];\n const newConnection = opts.forceNew ||\n opts[\"force new connection\"] ||\n false === opts.multiplex ||\n sameNamespace;\n let io;\n if (newConnection) {\n debug(\"ignoring socket cache for %s\", source);\n io = new manager_js_1.Manager(source, opts);\n }\n else {\n if (!cache[id]) {\n debug(\"new io instance for %s\", source);\n cache[id] = new manager_js_1.Manager(source, opts);\n }\n io = cache[id];\n }\n if (parsed.query && !opts.query) {\n opts.query = parsed.queryKey;\n }\n return io.socket(parsed.path, opts);\n}\nexports.io = lookup;\nexports.connect = lookup;\nexports[\"default\"] = lookup;\n// so that \"lookup\" can be used both as a function (e.g. `io(...)`) and as a\n// namespace (e.g. `io.connect(...)`), for backward compatibility\nObject.assign(lookup, {\n Manager: manager_js_1.Manager,\n Socket: socket_js_1.Socket,\n io: lookup,\n connect: lookup,\n});\n/**\n * Protocol version.\n *\n * @public\n */\nvar socket_io_parser_1 = __webpack_require__(/*! socket.io-parser */ \"./node_modules/socket.io-parser/build/cjs/index.js\");\nObject.defineProperty(exports, \"protocol\", ({ enumerable: true, get: function () { return socket_io_parser_1.protocol; } }));\n\nmodule.exports = lookup;\n\n\n//# sourceURL=webpack://dcp/./node_modules/socket.io-client/build/cjs/index.js?");
|
|
5758
|
+
eval("\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports[\"default\"] = exports.connect = exports.io = exports.Socket = exports.Manager = exports.protocol = void 0;\nconst url_js_1 = __webpack_require__(/*! ./url.js */ \"./node_modules/socket.io-client/build/cjs/url.js\");\nconst manager_js_1 = __webpack_require__(/*! ./manager.js */ \"./node_modules/socket.io-client/build/cjs/manager.js\");\nObject.defineProperty(exports, \"Manager\", ({ enumerable: true, get: function () { return manager_js_1.Manager; } }));\nconst socket_js_1 = __webpack_require__(/*! ./socket.js */ \"./node_modules/socket.io-client/build/cjs/socket.js\");\nObject.defineProperty(exports, \"Socket\", ({ enumerable: true, get: function () { return socket_js_1.Socket; } }));\nconst debug_1 = __importDefault(__webpack_require__(/*! debug */ \"./node_modules/socket.io-client/node_modules/debug/src/browser.js\")); // debug()\nconst debug = debug_1.default(\"socket.io-client\"); // debug()\n/**\n * Managers cache.\n */\nconst cache = {};\nfunction lookup(uri, opts) {\n if (typeof uri === \"object\") {\n opts = uri;\n uri = undefined;\n }\n opts = opts || {};\n const parsed = url_js_1.url(uri, opts.path || \"/socket.io\");\n const source = parsed.source;\n const id = parsed.id;\n const path = parsed.path;\n const sameNamespace = cache[id] && path in cache[id][\"nsps\"];\n const newConnection = opts.forceNew ||\n opts[\"force new connection\"] ||\n false === opts.multiplex ||\n sameNamespace;\n let io;\n if (newConnection) {\n debug(\"ignoring socket cache for %s\", source);\n io = new manager_js_1.Manager(source, opts);\n }\n else {\n if (!cache[id]) {\n debug(\"new io instance for %s\", source);\n cache[id] = new manager_js_1.Manager(source, opts);\n }\n io = cache[id];\n }\n if (parsed.query && !opts.query) {\n opts.query = parsed.queryKey;\n }\n return io.socket(parsed.path, opts);\n}\nexports.io = lookup;\nexports.connect = lookup;\nexports[\"default\"] = lookup;\n// so that \"lookup\" can be used both as a function (e.g. `io(...)`) and as a\n// namespace (e.g. `io.connect(...)`), for backward compatibility\nObject.assign(lookup, {\n Manager: manager_js_1.Manager,\n Socket: socket_js_1.Socket,\n io: lookup,\n connect: lookup,\n});\n/**\n * Protocol version.\n *\n * @public\n */\nvar socket_io_parser_1 = __webpack_require__(/*! socket.io-parser */ \"./node_modules/socket.io-parser/build/cjs/index.js\");\nObject.defineProperty(exports, \"protocol\", ({ enumerable: true, get: function () { return socket_io_parser_1.protocol; } }));\n\nmodule.exports = lookup;\n\n\n//# sourceURL=webpack://dcp/./node_modules/socket.io-client/build/cjs/index.js?");
|
|
5642
5759
|
|
|
5643
5760
|
/***/ }),
|
|
5644
5761
|
|
|
@@ -5649,7 +5766,7 @@ eval("\nvar __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5649
5766
|
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
5650
5767
|
|
|
5651
5768
|
"use strict";
|
|
5652
|
-
eval("\nvar __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });\n}) : (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n o[k2] = m[k];\n}));\nvar __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\n}) : function(o, v) {\n o[\"default\"] = v;\n});\nvar __importStar = (this && this.__importStar) || function (mod) {\n if (mod && mod.__esModule) return mod;\n var result = {};\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\n __setModuleDefault(result, mod);\n return result;\n};\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.Manager = void 0;\nconst engine_io_client_1 = __webpack_require__(/*! engine.io-client */ \"./node_modules/engine.io-client/build/cjs/index.js\");\nconst socket_js_1 = __webpack_require__(/*! ./socket.js */ \"./node_modules/socket.io-client/build/cjs/socket.js\");\nconst parser = __importStar(__webpack_require__(/*! socket.io-parser */ \"./node_modules/socket.io-parser/build/cjs/index.js\"));\nconst on_js_1 = __webpack_require__(/*! ./on.js */ \"./node_modules/socket.io-client/build/cjs/on.js\");\nconst backo2_js_1 = __webpack_require__(/*! ./contrib/backo2.js */ \"./node_modules/socket.io-client/build/cjs/contrib/backo2.js\");\nconst component_emitter_1 = __webpack_require__(/*! @socket.io/component-emitter */ \"./node_modules/@socket.io/component-emitter/index.mjs\");\nconst debug_1 = __importDefault(__webpack_require__(/*! debug */ \"./node_modules/debug/src/browser.js\")); // debug()\nconst debug = debug_1.default(\"socket.io-client:manager\"); // debug()\nclass Manager extends component_emitter_1.Emitter {\n constructor(uri, opts) {\n var _a;\n super();\n this.nsps = {};\n this.subs = [];\n if (uri && \"object\" === typeof uri) {\n opts = uri;\n uri = undefined;\n }\n opts = opts || {};\n opts.path = opts.path || \"/socket.io\";\n this.opts = opts;\n engine_io_client_1.installTimerFunctions(this, opts);\n this.reconnection(opts.reconnection !== false);\n this.reconnectionAttempts(opts.reconnectionAttempts || Infinity);\n this.reconnectionDelay(opts.reconnectionDelay || 1000);\n this.reconnectionDelayMax(opts.reconnectionDelayMax || 5000);\n this.randomizationFactor((_a = opts.randomizationFactor) !== null && _a !== void 0 ? _a : 0.5);\n this.backoff = new backo2_js_1.Backoff({\n min: this.reconnectionDelay(),\n max: this.reconnectionDelayMax(),\n jitter: this.randomizationFactor(),\n });\n this.timeout(null == opts.timeout ? 20000 : opts.timeout);\n this._readyState = \"closed\";\n this.uri = uri;\n const _parser = opts.parser || parser;\n this.encoder = new _parser.Encoder();\n this.decoder = new _parser.Decoder();\n this._autoConnect = opts.autoConnect !== false;\n if (this._autoConnect)\n this.open();\n }\n reconnection(v) {\n if (!arguments.length)\n return this._reconnection;\n this._reconnection = !!v;\n return this;\n }\n reconnectionAttempts(v) {\n if (v === undefined)\n return this._reconnectionAttempts;\n this._reconnectionAttempts = v;\n return this;\n }\n reconnectionDelay(v) {\n var _a;\n if (v === undefined)\n return this._reconnectionDelay;\n this._reconnectionDelay = v;\n (_a = this.backoff) === null || _a === void 0 ? void 0 : _a.setMin(v);\n return this;\n }\n randomizationFactor(v) {\n var _a;\n if (v === undefined)\n return this._randomizationFactor;\n this._randomizationFactor = v;\n (_a = this.backoff) === null || _a === void 0 ? void 0 : _a.setJitter(v);\n return this;\n }\n reconnectionDelayMax(v) {\n var _a;\n if (v === undefined)\n return this._reconnectionDelayMax;\n this._reconnectionDelayMax = v;\n (_a = this.backoff) === null || _a === void 0 ? void 0 : _a.setMax(v);\n return this;\n }\n timeout(v) {\n if (!arguments.length)\n return this._timeout;\n this._timeout = v;\n return this;\n }\n /**\n * Starts trying to reconnect if reconnection is enabled and we have not\n * started reconnecting yet\n *\n * @private\n */\n maybeReconnectOnOpen() {\n // Only try to reconnect if it's the first time we're connecting\n if (!this._reconnecting &&\n this._reconnection &&\n this.backoff.attempts === 0) {\n // keeps reconnection from firing twice for the same reconnection loop\n this.reconnect();\n }\n }\n /**\n * Sets the current transport `socket`.\n *\n * @param {Function} fn - optional, callback\n * @return self\n * @public\n */\n open(fn) {\n debug(\"readyState %s\", this._readyState);\n if (~this._readyState.indexOf(\"open\"))\n return this;\n debug(\"opening %s\", this.uri);\n this.engine = new engine_io_client_1.Socket(this.uri, this.opts);\n const socket = this.engine;\n const self = this;\n this._readyState = \"opening\";\n this.skipReconnect = false;\n // emit `open`\n const openSubDestroy = on_js_1.on(socket, \"open\", function () {\n self.onopen();\n fn && fn();\n });\n // emit `error`\n const errorSub = on_js_1.on(socket, \"error\", (err) => {\n debug(\"error\");\n self.cleanup();\n self._readyState = \"closed\";\n this.emitReserved(\"error\", err);\n if (fn) {\n fn(err);\n }\n else {\n // Only do this if there is no fn to handle the error\n self.maybeReconnectOnOpen();\n }\n });\n if (false !== this._timeout) {\n const timeout = this._timeout;\n debug(\"connect attempt will timeout after %d\", timeout);\n if (timeout === 0) {\n openSubDestroy(); // prevents a race condition with the 'open' event\n }\n // set timer\n const timer = this.setTimeoutFn(() => {\n debug(\"connect attempt timed out after %d\", timeout);\n openSubDestroy();\n socket.close();\n // @ts-ignore\n socket.emit(\"error\", new Error(\"timeout\"));\n }, timeout);\n if (this.opts.autoUnref) {\n timer.unref();\n }\n this.subs.push(function subDestroy() {\n clearTimeout(timer);\n });\n }\n this.subs.push(openSubDestroy);\n this.subs.push(errorSub);\n return this;\n }\n /**\n * Alias for open()\n *\n * @return self\n * @public\n */\n connect(fn) {\n return this.open(fn);\n }\n /**\n * Called upon transport open.\n *\n * @private\n */\n onopen() {\n debug(\"open\");\n // clear old subs\n this.cleanup();\n // mark as open\n this._readyState = \"open\";\n this.emitReserved(\"open\");\n // add new subs\n const socket = this.engine;\n this.subs.push(on_js_1.on(socket, \"ping\", this.onping.bind(this)), on_js_1.on(socket, \"data\", this.ondata.bind(this)), on_js_1.on(socket, \"error\", this.onerror.bind(this)), on_js_1.on(socket, \"close\", this.onclose.bind(this)), on_js_1.on(this.decoder, \"decoded\", this.ondecoded.bind(this)));\n }\n /**\n * Called upon a ping.\n *\n * @private\n */\n onping() {\n this.emitReserved(\"ping\");\n }\n /**\n * Called with data.\n *\n * @private\n */\n ondata(data) {\n try {\n this.decoder.add(data);\n }\n catch (e) {\n this.onclose(\"parse error\", e);\n }\n }\n /**\n * Called when parser fully decodes a packet.\n *\n * @private\n */\n ondecoded(packet) {\n // the nextTick call prevents an exception in a user-provided event listener from triggering a disconnection due to a \"parse error\"\n engine_io_client_1.nextTick(() => {\n this.emitReserved(\"packet\", packet);\n }, this.setTimeoutFn);\n }\n /**\n * Called upon socket error.\n *\n * @private\n */\n onerror(err) {\n debug(\"error\", err);\n this.emitReserved(\"error\", err);\n }\n /**\n * Creates a new socket for the given `nsp`.\n *\n * @return {Socket}\n * @public\n */\n socket(nsp, opts) {\n let socket = this.nsps[nsp];\n if (!socket) {\n socket = new socket_js_1.Socket(this, nsp, opts);\n this.nsps[nsp] = socket;\n }\n return socket;\n }\n /**\n * Called upon a socket close.\n *\n * @param socket\n * @private\n */\n _destroy(socket) {\n const nsps = Object.keys(this.nsps);\n for (const nsp of nsps) {\n const socket = this.nsps[nsp];\n if (socket.active) {\n debug(\"socket %s is still active, skipping close\", nsp);\n return;\n }\n }\n this._close();\n }\n /**\n * Writes a packet.\n *\n * @param packet\n * @private\n */\n _packet(packet) {\n debug(\"writing packet %j\", packet);\n const encodedPackets = this.encoder.encode(packet);\n for (let i = 0; i < encodedPackets.length; i++) {\n this.engine.write(encodedPackets[i], packet.options);\n }\n }\n /**\n * Clean up transport subscriptions and packet buffer.\n *\n * @private\n */\n cleanup() {\n debug(\"cleanup\");\n this.subs.forEach((subDestroy) => subDestroy());\n this.subs.length = 0;\n this.decoder.destroy();\n }\n /**\n * Close the current socket.\n *\n * @private\n */\n _close() {\n debug(\"disconnect\");\n this.skipReconnect = true;\n this._reconnecting = false;\n this.onclose(\"forced close\");\n if (this.engine)\n this.engine.close();\n }\n /**\n * Alias for close()\n *\n * @private\n */\n disconnect() {\n return this._close();\n }\n /**\n * Called upon engine close.\n *\n * @private\n */\n onclose(reason, description) {\n debug(\"closed due to %s\", reason);\n this.cleanup();\n this.backoff.reset();\n this._readyState = \"closed\";\n this.emitReserved(\"close\", reason, description);\n if (this._reconnection && !this.skipReconnect) {\n this.reconnect();\n }\n }\n /**\n * Attempt a reconnection.\n *\n * @private\n */\n reconnect() {\n if (this._reconnecting || this.skipReconnect)\n return this;\n const self = this;\n if (this.backoff.attempts >= this._reconnectionAttempts) {\n debug(\"reconnect failed\");\n this.backoff.reset();\n this.emitReserved(\"reconnect_failed\");\n this._reconnecting = false;\n }\n else {\n const delay = this.backoff.duration();\n debug(\"will wait %dms before reconnect attempt\", delay);\n this._reconnecting = true;\n const timer = this.setTimeoutFn(() => {\n if (self.skipReconnect)\n return;\n debug(\"attempting reconnect\");\n this.emitReserved(\"reconnect_attempt\", self.backoff.attempts);\n // check again for the case socket closed in above events\n if (self.skipReconnect)\n return;\n self.open((err) => {\n if (err) {\n debug(\"reconnect attempt error\");\n self._reconnecting = false;\n self.reconnect();\n this.emitReserved(\"reconnect_error\", err);\n }\n else {\n debug(\"reconnect success\");\n self.onreconnect();\n }\n });\n }, delay);\n if (this.opts.autoUnref) {\n timer.unref();\n }\n this.subs.push(function subDestroy() {\n clearTimeout(timer);\n });\n }\n }\n /**\n * Called upon successful reconnect.\n *\n * @private\n */\n onreconnect() {\n const attempt = this.backoff.attempts;\n this._reconnecting = false;\n this.backoff.reset();\n this.emitReserved(\"reconnect\", attempt);\n }\n}\nexports.Manager = Manager;\n\n\n//# sourceURL=webpack://dcp/./node_modules/socket.io-client/build/cjs/manager.js?");
|
|
5769
|
+
eval("\nvar __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });\n}) : (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n o[k2] = m[k];\n}));\nvar __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\n}) : function(o, v) {\n o[\"default\"] = v;\n});\nvar __importStar = (this && this.__importStar) || function (mod) {\n if (mod && mod.__esModule) return mod;\n var result = {};\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\n __setModuleDefault(result, mod);\n return result;\n};\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.Manager = void 0;\nconst engine_io_client_1 = __webpack_require__(/*! engine.io-client */ \"./node_modules/engine.io-client/build/cjs/index.js\");\nconst socket_js_1 = __webpack_require__(/*! ./socket.js */ \"./node_modules/socket.io-client/build/cjs/socket.js\");\nconst parser = __importStar(__webpack_require__(/*! socket.io-parser */ \"./node_modules/socket.io-parser/build/cjs/index.js\"));\nconst on_js_1 = __webpack_require__(/*! ./on.js */ \"./node_modules/socket.io-client/build/cjs/on.js\");\nconst backo2_js_1 = __webpack_require__(/*! ./contrib/backo2.js */ \"./node_modules/socket.io-client/build/cjs/contrib/backo2.js\");\nconst component_emitter_1 = __webpack_require__(/*! @socket.io/component-emitter */ \"./node_modules/@socket.io/component-emitter/index.mjs\");\nconst debug_1 = __importDefault(__webpack_require__(/*! debug */ \"./node_modules/socket.io-client/node_modules/debug/src/browser.js\")); // debug()\nconst debug = debug_1.default(\"socket.io-client:manager\"); // debug()\nclass Manager extends component_emitter_1.Emitter {\n constructor(uri, opts) {\n var _a;\n super();\n this.nsps = {};\n this.subs = [];\n if (uri && \"object\" === typeof uri) {\n opts = uri;\n uri = undefined;\n }\n opts = opts || {};\n opts.path = opts.path || \"/socket.io\";\n this.opts = opts;\n engine_io_client_1.installTimerFunctions(this, opts);\n this.reconnection(opts.reconnection !== false);\n this.reconnectionAttempts(opts.reconnectionAttempts || Infinity);\n this.reconnectionDelay(opts.reconnectionDelay || 1000);\n this.reconnectionDelayMax(opts.reconnectionDelayMax || 5000);\n this.randomizationFactor((_a = opts.randomizationFactor) !== null && _a !== void 0 ? _a : 0.5);\n this.backoff = new backo2_js_1.Backoff({\n min: this.reconnectionDelay(),\n max: this.reconnectionDelayMax(),\n jitter: this.randomizationFactor(),\n });\n this.timeout(null == opts.timeout ? 20000 : opts.timeout);\n this._readyState = \"closed\";\n this.uri = uri;\n const _parser = opts.parser || parser;\n this.encoder = new _parser.Encoder();\n this.decoder = new _parser.Decoder();\n this._autoConnect = opts.autoConnect !== false;\n if (this._autoConnect)\n this.open();\n }\n reconnection(v) {\n if (!arguments.length)\n return this._reconnection;\n this._reconnection = !!v;\n return this;\n }\n reconnectionAttempts(v) {\n if (v === undefined)\n return this._reconnectionAttempts;\n this._reconnectionAttempts = v;\n return this;\n }\n reconnectionDelay(v) {\n var _a;\n if (v === undefined)\n return this._reconnectionDelay;\n this._reconnectionDelay = v;\n (_a = this.backoff) === null || _a === void 0 ? void 0 : _a.setMin(v);\n return this;\n }\n randomizationFactor(v) {\n var _a;\n if (v === undefined)\n return this._randomizationFactor;\n this._randomizationFactor = v;\n (_a = this.backoff) === null || _a === void 0 ? void 0 : _a.setJitter(v);\n return this;\n }\n reconnectionDelayMax(v) {\n var _a;\n if (v === undefined)\n return this._reconnectionDelayMax;\n this._reconnectionDelayMax = v;\n (_a = this.backoff) === null || _a === void 0 ? void 0 : _a.setMax(v);\n return this;\n }\n timeout(v) {\n if (!arguments.length)\n return this._timeout;\n this._timeout = v;\n return this;\n }\n /**\n * Starts trying to reconnect if reconnection is enabled and we have not\n * started reconnecting yet\n *\n * @private\n */\n maybeReconnectOnOpen() {\n // Only try to reconnect if it's the first time we're connecting\n if (!this._reconnecting &&\n this._reconnection &&\n this.backoff.attempts === 0) {\n // keeps reconnection from firing twice for the same reconnection loop\n this.reconnect();\n }\n }\n /**\n * Sets the current transport `socket`.\n *\n * @param {Function} fn - optional, callback\n * @return self\n * @public\n */\n open(fn) {\n debug(\"readyState %s\", this._readyState);\n if (~this._readyState.indexOf(\"open\"))\n return this;\n debug(\"opening %s\", this.uri);\n this.engine = new engine_io_client_1.Socket(this.uri, this.opts);\n const socket = this.engine;\n const self = this;\n this._readyState = \"opening\";\n this.skipReconnect = false;\n // emit `open`\n const openSubDestroy = on_js_1.on(socket, \"open\", function () {\n self.onopen();\n fn && fn();\n });\n // emit `error`\n const errorSub = on_js_1.on(socket, \"error\", (err) => {\n debug(\"error\");\n self.cleanup();\n self._readyState = \"closed\";\n this.emitReserved(\"error\", err);\n if (fn) {\n fn(err);\n }\n else {\n // Only do this if there is no fn to handle the error\n self.maybeReconnectOnOpen();\n }\n });\n if (false !== this._timeout) {\n const timeout = this._timeout;\n debug(\"connect attempt will timeout after %d\", timeout);\n if (timeout === 0) {\n openSubDestroy(); // prevents a race condition with the 'open' event\n }\n // set timer\n const timer = this.setTimeoutFn(() => {\n debug(\"connect attempt timed out after %d\", timeout);\n openSubDestroy();\n socket.close();\n // @ts-ignore\n socket.emit(\"error\", new Error(\"timeout\"));\n }, timeout);\n if (this.opts.autoUnref) {\n timer.unref();\n }\n this.subs.push(function subDestroy() {\n clearTimeout(timer);\n });\n }\n this.subs.push(openSubDestroy);\n this.subs.push(errorSub);\n return this;\n }\n /**\n * Alias for open()\n *\n * @return self\n * @public\n */\n connect(fn) {\n return this.open(fn);\n }\n /**\n * Called upon transport open.\n *\n * @private\n */\n onopen() {\n debug(\"open\");\n // clear old subs\n this.cleanup();\n // mark as open\n this._readyState = \"open\";\n this.emitReserved(\"open\");\n // add new subs\n const socket = this.engine;\n this.subs.push(on_js_1.on(socket, \"ping\", this.onping.bind(this)), on_js_1.on(socket, \"data\", this.ondata.bind(this)), on_js_1.on(socket, \"error\", this.onerror.bind(this)), on_js_1.on(socket, \"close\", this.onclose.bind(this)), on_js_1.on(this.decoder, \"decoded\", this.ondecoded.bind(this)));\n }\n /**\n * Called upon a ping.\n *\n * @private\n */\n onping() {\n this.emitReserved(\"ping\");\n }\n /**\n * Called with data.\n *\n * @private\n */\n ondata(data) {\n try {\n this.decoder.add(data);\n }\n catch (e) {\n this.onclose(\"parse error\", e);\n }\n }\n /**\n * Called when parser fully decodes a packet.\n *\n * @private\n */\n ondecoded(packet) {\n // the nextTick call prevents an exception in a user-provided event listener from triggering a disconnection due to a \"parse error\"\n engine_io_client_1.nextTick(() => {\n this.emitReserved(\"packet\", packet);\n }, this.setTimeoutFn);\n }\n /**\n * Called upon socket error.\n *\n * @private\n */\n onerror(err) {\n debug(\"error\", err);\n this.emitReserved(\"error\", err);\n }\n /**\n * Creates a new socket for the given `nsp`.\n *\n * @return {Socket}\n * @public\n */\n socket(nsp, opts) {\n let socket = this.nsps[nsp];\n if (!socket) {\n socket = new socket_js_1.Socket(this, nsp, opts);\n this.nsps[nsp] = socket;\n }\n return socket;\n }\n /**\n * Called upon a socket close.\n *\n * @param socket\n * @private\n */\n _destroy(socket) {\n const nsps = Object.keys(this.nsps);\n for (const nsp of nsps) {\n const socket = this.nsps[nsp];\n if (socket.active) {\n debug(\"socket %s is still active, skipping close\", nsp);\n return;\n }\n }\n this._close();\n }\n /**\n * Writes a packet.\n *\n * @param packet\n * @private\n */\n _packet(packet) {\n debug(\"writing packet %j\", packet);\n const encodedPackets = this.encoder.encode(packet);\n for (let i = 0; i < encodedPackets.length; i++) {\n this.engine.write(encodedPackets[i], packet.options);\n }\n }\n /**\n * Clean up transport subscriptions and packet buffer.\n *\n * @private\n */\n cleanup() {\n debug(\"cleanup\");\n this.subs.forEach((subDestroy) => subDestroy());\n this.subs.length = 0;\n this.decoder.destroy();\n }\n /**\n * Close the current socket.\n *\n * @private\n */\n _close() {\n debug(\"disconnect\");\n this.skipReconnect = true;\n this._reconnecting = false;\n this.onclose(\"forced close\");\n if (this.engine)\n this.engine.close();\n }\n /**\n * Alias for close()\n *\n * @private\n */\n disconnect() {\n return this._close();\n }\n /**\n * Called upon engine close.\n *\n * @private\n */\n onclose(reason, description) {\n debug(\"closed due to %s\", reason);\n this.cleanup();\n this.backoff.reset();\n this._readyState = \"closed\";\n this.emitReserved(\"close\", reason, description);\n if (this._reconnection && !this.skipReconnect) {\n this.reconnect();\n }\n }\n /**\n * Attempt a reconnection.\n *\n * @private\n */\n reconnect() {\n if (this._reconnecting || this.skipReconnect)\n return this;\n const self = this;\n if (this.backoff.attempts >= this._reconnectionAttempts) {\n debug(\"reconnect failed\");\n this.backoff.reset();\n this.emitReserved(\"reconnect_failed\");\n this._reconnecting = false;\n }\n else {\n const delay = this.backoff.duration();\n debug(\"will wait %dms before reconnect attempt\", delay);\n this._reconnecting = true;\n const timer = this.setTimeoutFn(() => {\n if (self.skipReconnect)\n return;\n debug(\"attempting reconnect\");\n this.emitReserved(\"reconnect_attempt\", self.backoff.attempts);\n // check again for the case socket closed in above events\n if (self.skipReconnect)\n return;\n self.open((err) => {\n if (err) {\n debug(\"reconnect attempt error\");\n self._reconnecting = false;\n self.reconnect();\n this.emitReserved(\"reconnect_error\", err);\n }\n else {\n debug(\"reconnect success\");\n self.onreconnect();\n }\n });\n }, delay);\n if (this.opts.autoUnref) {\n timer.unref();\n }\n this.subs.push(function subDestroy() {\n clearTimeout(timer);\n });\n }\n }\n /**\n * Called upon successful reconnect.\n *\n * @private\n */\n onreconnect() {\n const attempt = this.backoff.attempts;\n this._reconnecting = false;\n this.backoff.reset();\n this.emitReserved(\"reconnect\", attempt);\n }\n}\nexports.Manager = Manager;\n\n\n//# sourceURL=webpack://dcp/./node_modules/socket.io-client/build/cjs/manager.js?");
|
|
5653
5770
|
|
|
5654
5771
|
/***/ }),
|
|
5655
5772
|
|
|
@@ -5671,7 +5788,7 @@ eval("\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexpo
|
|
|
5671
5788
|
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
5672
5789
|
|
|
5673
5790
|
"use strict";
|
|
5674
|
-
eval("\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.Socket = void 0;\nconst socket_io_parser_1 = __webpack_require__(/*! socket.io-parser */ \"./node_modules/socket.io-parser/build/cjs/index.js\");\nconst on_js_1 = __webpack_require__(/*! ./on.js */ \"./node_modules/socket.io-client/build/cjs/on.js\");\nconst component_emitter_1 = __webpack_require__(/*! @socket.io/component-emitter */ \"./node_modules/@socket.io/component-emitter/index.mjs\");\nconst debug_1 = __importDefault(__webpack_require__(/*! debug */ \"./node_modules/debug/src/browser.js\")); // debug()\nconst debug = debug_1.default(\"socket.io-client:socket\"); // debug()\n/**\n * Internal events.\n * These events can't be emitted by the user.\n */\nconst RESERVED_EVENTS = Object.freeze({\n connect: 1,\n connect_error: 1,\n disconnect: 1,\n disconnecting: 1,\n // EventEmitter reserved events: https://nodejs.org/api/events.html#events_event_newlistener\n newListener: 1,\n removeListener: 1,\n});\n/**\n * A Socket is the fundamental class for interacting with the server.\n *\n * A Socket belongs to a certain Namespace (by default /) and uses an underlying {@link Manager} to communicate.\n *\n * @example\n * const socket = io();\n *\n * socket.on(\"connect\", () => {\n * console.log(\"connected\");\n * });\n *\n * // send an event to the server\n * socket.emit(\"foo\", \"bar\");\n *\n * socket.on(\"foobar\", () => {\n * // an event was received from the server\n * });\n *\n * // upon disconnection\n * socket.on(\"disconnect\", (reason) => {\n * console.log(`disconnected due to ${reason}`);\n * });\n */\nclass Socket extends component_emitter_1.Emitter {\n /**\n * `Socket` constructor.\n */\n constructor(io, nsp, opts) {\n super();\n /**\n * Whether the socket is currently connected to the server.\n *\n * @example\n * const socket = io();\n *\n * socket.on(\"connect\", () => {\n * console.log(socket.connected); // true\n * });\n *\n * socket.on(\"disconnect\", () => {\n * console.log(socket.connected); // false\n * });\n */\n this.connected = false;\n /**\n * Buffer for packets received before the CONNECT packet\n */\n this.receiveBuffer = [];\n /**\n * Buffer for packets that will be sent once the socket is connected\n */\n this.sendBuffer = [];\n this.ids = 0;\n this.acks = {};\n this.flags = {};\n this.io = io;\n this.nsp = nsp;\n if (opts && opts.auth) {\n this.auth = opts.auth;\n }\n if (this.io._autoConnect)\n this.open();\n }\n /**\n * Whether the socket is currently disconnected\n *\n * @example\n * const socket = io();\n *\n * socket.on(\"connect\", () => {\n * console.log(socket.disconnected); // false\n * });\n *\n * socket.on(\"disconnect\", () => {\n * console.log(socket.disconnected); // true\n * });\n */\n get disconnected() {\n return !this.connected;\n }\n /**\n * Subscribe to open, close and packet events\n *\n * @private\n */\n subEvents() {\n if (this.subs)\n return;\n const io = this.io;\n this.subs = [\n on_js_1.on(io, \"open\", this.onopen.bind(this)),\n on_js_1.on(io, \"packet\", this.onpacket.bind(this)),\n on_js_1.on(io, \"error\", this.onerror.bind(this)),\n on_js_1.on(io, \"close\", this.onclose.bind(this)),\n ];\n }\n /**\n * Whether the Socket will try to reconnect when its Manager connects or reconnects.\n *\n * @example\n * const socket = io();\n *\n * console.log(socket.active); // true\n *\n * socket.on(\"disconnect\", (reason) => {\n * if (reason === \"io server disconnect\") {\n * // the disconnection was initiated by the server, you need to manually reconnect\n * console.log(socket.active); // false\n * }\n * // else the socket will automatically try to reconnect\n * console.log(socket.active); // true\n * });\n */\n get active() {\n return !!this.subs;\n }\n /**\n * \"Opens\" the socket.\n *\n * @example\n * const socket = io({\n * autoConnect: false\n * });\n *\n * socket.connect();\n */\n connect() {\n if (this.connected)\n return this;\n this.subEvents();\n if (!this.io[\"_reconnecting\"])\n this.io.open(); // ensure open\n if (\"open\" === this.io._readyState)\n this.onopen();\n return this;\n }\n /**\n * Alias for {@link connect()}.\n */\n open() {\n return this.connect();\n }\n /**\n * Sends a `message` event.\n *\n * This method mimics the WebSocket.send() method.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send\n *\n * @example\n * socket.send(\"hello\");\n *\n * // this is equivalent to\n * socket.emit(\"message\", \"hello\");\n *\n * @return self\n */\n send(...args) {\n args.unshift(\"message\");\n this.emit.apply(this, args);\n return this;\n }\n /**\n * Override `emit`.\n * If the event is in `events`, it's emitted normally.\n *\n * @example\n * socket.emit(\"hello\", \"world\");\n *\n * // all serializable datastructures are supported (no need to call JSON.stringify)\n * socket.emit(\"hello\", 1, \"2\", { 3: [\"4\"], 5: Uint8Array.from([6]) });\n *\n * // with an acknowledgement from the server\n * socket.emit(\"hello\", \"world\", (val) => {\n * // ...\n * });\n *\n * @return self\n */\n emit(ev, ...args) {\n if (RESERVED_EVENTS.hasOwnProperty(ev)) {\n throw new Error('\"' + ev.toString() + '\" is a reserved event name');\n }\n args.unshift(ev);\n const packet = {\n type: socket_io_parser_1.PacketType.EVENT,\n data: args,\n };\n packet.options = {};\n packet.options.compress = this.flags.compress !== false;\n // event ack callback\n if (\"function\" === typeof args[args.length - 1]) {\n const id = this.ids++;\n debug(\"emitting packet with ack id %d\", id);\n const ack = args.pop();\n this._registerAckCallback(id, ack);\n packet.id = id;\n }\n const isTransportWritable = this.io.engine &&\n this.io.engine.transport &&\n this.io.engine.transport.writable;\n const discardPacket = this.flags.volatile && (!isTransportWritable || !this.connected);\n if (discardPacket) {\n debug(\"discard packet as the transport is not currently writable\");\n }\n else if (this.connected) {\n this.notifyOutgoingListeners(packet);\n this.packet(packet);\n }\n else {\n this.sendBuffer.push(packet);\n }\n this.flags = {};\n return this;\n }\n /**\n * @private\n */\n _registerAckCallback(id, ack) {\n const timeout = this.flags.timeout;\n if (timeout === undefined) {\n this.acks[id] = ack;\n return;\n }\n // @ts-ignore\n const timer = this.io.setTimeoutFn(() => {\n delete this.acks[id];\n for (let i = 0; i < this.sendBuffer.length; i++) {\n if (this.sendBuffer[i].id === id) {\n debug(\"removing packet with ack id %d from the buffer\", id);\n this.sendBuffer.splice(i, 1);\n }\n }\n debug(\"event with ack id %d has timed out after %d ms\", id, timeout);\n ack.call(this, new Error(\"operation has timed out\"));\n }, timeout);\n this.acks[id] = (...args) => {\n // @ts-ignore\n this.io.clearTimeoutFn(timer);\n ack.apply(this, [null, ...args]);\n };\n }\n /**\n * Sends a packet.\n *\n * @param packet\n * @private\n */\n packet(packet) {\n packet.nsp = this.nsp;\n this.io._packet(packet);\n }\n /**\n * Called upon engine `open`.\n *\n * @private\n */\n onopen() {\n debug(\"transport is open - connecting\");\n if (typeof this.auth == \"function\") {\n this.auth((data) => {\n this.packet({ type: socket_io_parser_1.PacketType.CONNECT, data });\n });\n }\n else {\n this.packet({ type: socket_io_parser_1.PacketType.CONNECT, data: this.auth });\n }\n }\n /**\n * Called upon engine or manager `error`.\n *\n * @param err\n * @private\n */\n onerror(err) {\n if (!this.connected) {\n this.emitReserved(\"connect_error\", err);\n }\n }\n /**\n * Called upon engine `close`.\n *\n * @param reason\n * @param description\n * @private\n */\n onclose(reason, description) {\n debug(\"close (%s)\", reason);\n this.connected = false;\n delete this.id;\n this.emitReserved(\"disconnect\", reason, description);\n }\n /**\n * Called with socket packet.\n *\n * @param packet\n * @private\n */\n onpacket(packet) {\n const sameNamespace = packet.nsp === this.nsp;\n if (!sameNamespace)\n return;\n switch (packet.type) {\n case socket_io_parser_1.PacketType.CONNECT:\n if (packet.data && packet.data.sid) {\n const id = packet.data.sid;\n this.onconnect(id);\n }\n else {\n this.emitReserved(\"connect_error\", new Error(\"It seems you are trying to reach a Socket.IO server in v2.x with a v3.x client, but they are not compatible (more information here: https://socket.io/docs/v3/migrating-from-2-x-to-3-0/)\"));\n }\n break;\n case socket_io_parser_1.PacketType.EVENT:\n case socket_io_parser_1.PacketType.BINARY_EVENT:\n this.onevent(packet);\n break;\n case socket_io_parser_1.PacketType.ACK:\n case socket_io_parser_1.PacketType.BINARY_ACK:\n this.onack(packet);\n break;\n case socket_io_parser_1.PacketType.DISCONNECT:\n this.ondisconnect();\n break;\n case socket_io_parser_1.PacketType.CONNECT_ERROR:\n this.destroy();\n const err = new Error(packet.data.message);\n // @ts-ignore\n err.data = packet.data.data;\n this.emitReserved(\"connect_error\", err);\n break;\n }\n }\n /**\n * Called upon a server event.\n *\n * @param packet\n * @private\n */\n onevent(packet) {\n const args = packet.data || [];\n debug(\"emitting event %j\", args);\n if (null != packet.id) {\n debug(\"attaching ack callback to event\");\n args.push(this.ack(packet.id));\n }\n if (this.connected) {\n this.emitEvent(args);\n }\n else {\n this.receiveBuffer.push(Object.freeze(args));\n }\n }\n emitEvent(args) {\n if (this._anyListeners && this._anyListeners.length) {\n const listeners = this._anyListeners.slice();\n for (const listener of listeners) {\n listener.apply(this, args);\n }\n }\n super.emit.apply(this, args);\n }\n /**\n * Produces an ack callback to emit with an event.\n *\n * @private\n */\n ack(id) {\n const self = this;\n let sent = false;\n return function (...args) {\n // prevent double callbacks\n if (sent)\n return;\n sent = true;\n debug(\"sending ack %j\", args);\n self.packet({\n type: socket_io_parser_1.PacketType.ACK,\n id: id,\n data: args,\n });\n };\n }\n /**\n * Called upon a server acknowlegement.\n *\n * @param packet\n * @private\n */\n onack(packet) {\n const ack = this.acks[packet.id];\n if (\"function\" === typeof ack) {\n debug(\"calling ack %s with %j\", packet.id, packet.data);\n ack.apply(this, packet.data);\n delete this.acks[packet.id];\n }\n else {\n debug(\"bad ack %s\", packet.id);\n }\n }\n /**\n * Called upon server connect.\n *\n * @private\n */\n onconnect(id) {\n debug(\"socket connected with id %s\", id);\n this.id = id;\n this.connected = true;\n this.emitBuffered();\n this.emitReserved(\"connect\");\n }\n /**\n * Emit buffered events (received and emitted).\n *\n * @private\n */\n emitBuffered() {\n this.receiveBuffer.forEach((args) => this.emitEvent(args));\n this.receiveBuffer = [];\n this.sendBuffer.forEach((packet) => {\n this.notifyOutgoingListeners(packet);\n this.packet(packet);\n });\n this.sendBuffer = [];\n }\n /**\n * Called upon server disconnect.\n *\n * @private\n */\n ondisconnect() {\n debug(\"server disconnect (%s)\", this.nsp);\n this.destroy();\n this.onclose(\"io server disconnect\");\n }\n /**\n * Called upon forced client/server side disconnections,\n * this method ensures the manager stops tracking us and\n * that reconnections don't get triggered for this.\n *\n * @private\n */\n destroy() {\n if (this.subs) {\n // clean subscriptions to avoid reconnections\n this.subs.forEach((subDestroy) => subDestroy());\n this.subs = undefined;\n }\n this.io[\"_destroy\"](this);\n }\n /**\n * Disconnects the socket manually. In that case, the socket will not try to reconnect.\n *\n * If this is the last active Socket instance of the {@link Manager}, the low-level connection will be closed.\n *\n * @example\n * const socket = io();\n *\n * socket.on(\"disconnect\", (reason) => {\n * // console.log(reason); prints \"io client disconnect\"\n * });\n *\n * socket.disconnect();\n *\n * @return self\n */\n disconnect() {\n if (this.connected) {\n debug(\"performing disconnect (%s)\", this.nsp);\n this.packet({ type: socket_io_parser_1.PacketType.DISCONNECT });\n }\n // remove socket from pool\n this.destroy();\n if (this.connected) {\n // fire events\n this.onclose(\"io client disconnect\");\n }\n return this;\n }\n /**\n * Alias for {@link disconnect()}.\n *\n * @return self\n */\n close() {\n return this.disconnect();\n }\n /**\n * Sets the compress flag.\n *\n * @example\n * socket.compress(false).emit(\"hello\");\n *\n * @param compress - if `true`, compresses the sending data\n * @return self\n */\n compress(compress) {\n this.flags.compress = compress;\n return this;\n }\n /**\n * Sets a modifier for a subsequent event emission that the event message will be dropped when this socket is not\n * ready to send messages.\n *\n * @example\n * socket.volatile.emit(\"hello\"); // the server may or may not receive it\n *\n * @returns self\n */\n get volatile() {\n this.flags.volatile = true;\n return this;\n }\n /**\n * Sets a modifier for a subsequent event emission that the callback will be called with an error when the\n * given number of milliseconds have elapsed without an acknowledgement from the server:\n *\n * @example\n * socket.timeout(5000).emit(\"my-event\", (err) => {\n * if (err) {\n * // the server did not acknowledge the event in the given delay\n * }\n * });\n *\n * @returns self\n */\n timeout(timeout) {\n this.flags.timeout = timeout;\n return this;\n }\n /**\n * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the\n * callback.\n *\n * @example\n * socket.onAny((event, ...args) => {\n * console.log(`got ${event}`);\n * });\n *\n * @param listener\n */\n onAny(listener) {\n this._anyListeners = this._anyListeners || [];\n this._anyListeners.push(listener);\n return this;\n }\n /**\n * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the\n * callback. The listener is added to the beginning of the listeners array.\n *\n * @example\n * socket.prependAny((event, ...args) => {\n * console.log(`got event ${event}`);\n * });\n *\n * @param listener\n */\n prependAny(listener) {\n this._anyListeners = this._anyListeners || [];\n this._anyListeners.unshift(listener);\n return this;\n }\n /**\n * Removes the listener that will be fired when any event is emitted.\n *\n * @example\n * const catchAllListener = (event, ...args) => {\n * console.log(`got event ${event}`);\n * }\n *\n * socket.onAny(catchAllListener);\n *\n * // remove a specific listener\n * socket.offAny(catchAllListener);\n *\n * // or remove all listeners\n * socket.offAny();\n *\n * @param listener\n */\n offAny(listener) {\n if (!this._anyListeners) {\n return this;\n }\n if (listener) {\n const listeners = this._anyListeners;\n for (let i = 0; i < listeners.length; i++) {\n if (listener === listeners[i]) {\n listeners.splice(i, 1);\n return this;\n }\n }\n }\n else {\n this._anyListeners = [];\n }\n return this;\n }\n /**\n * Returns an array of listeners that are listening for any event that is specified. This array can be manipulated,\n * e.g. to remove listeners.\n */\n listenersAny() {\n return this._anyListeners || [];\n }\n /**\n * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the\n * callback.\n *\n * Note: acknowledgements sent to the server are not included.\n *\n * @example\n * socket.onAnyOutgoing((event, ...args) => {\n * console.log(`sent event ${event}`);\n * });\n *\n * @param listener\n */\n onAnyOutgoing(listener) {\n this._anyOutgoingListeners = this._anyOutgoingListeners || [];\n this._anyOutgoingListeners.push(listener);\n return this;\n }\n /**\n * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the\n * callback. The listener is added to the beginning of the listeners array.\n *\n * Note: acknowledgements sent to the server are not included.\n *\n * @example\n * socket.prependAnyOutgoing((event, ...args) => {\n * console.log(`sent event ${event}`);\n * });\n *\n * @param listener\n */\n prependAnyOutgoing(listener) {\n this._anyOutgoingListeners = this._anyOutgoingListeners || [];\n this._anyOutgoingListeners.unshift(listener);\n return this;\n }\n /**\n * Removes the listener that will be fired when any event is emitted.\n *\n * @example\n * const catchAllListener = (event, ...args) => {\n * console.log(`sent event ${event}`);\n * }\n *\n * socket.onAnyOutgoing(catchAllListener);\n *\n * // remove a specific listener\n * socket.offAnyOutgoing(catchAllListener);\n *\n * // or remove all listeners\n * socket.offAnyOutgoing();\n *\n * @param [listener] - the catch-all listener (optional)\n */\n offAnyOutgoing(listener) {\n if (!this._anyOutgoingListeners) {\n return this;\n }\n if (listener) {\n const listeners = this._anyOutgoingListeners;\n for (let i = 0; i < listeners.length; i++) {\n if (listener === listeners[i]) {\n listeners.splice(i, 1);\n return this;\n }\n }\n }\n else {\n this._anyOutgoingListeners = [];\n }\n return this;\n }\n /**\n * Returns an array of listeners that are listening for any event that is specified. This array can be manipulated,\n * e.g. to remove listeners.\n */\n listenersAnyOutgoing() {\n return this._anyOutgoingListeners || [];\n }\n /**\n * Notify the listeners for each packet sent\n *\n * @param packet\n *\n * @private\n */\n notifyOutgoingListeners(packet) {\n if (this._anyOutgoingListeners && this._anyOutgoingListeners.length) {\n const listeners = this._anyOutgoingListeners.slice();\n for (const listener of listeners) {\n listener.apply(this, packet.data);\n }\n }\n }\n}\nexports.Socket = Socket;\n\n\n//# sourceURL=webpack://dcp/./node_modules/socket.io-client/build/cjs/socket.js?");
|
|
5791
|
+
eval("\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.Socket = void 0;\nconst socket_io_parser_1 = __webpack_require__(/*! socket.io-parser */ \"./node_modules/socket.io-parser/build/cjs/index.js\");\nconst on_js_1 = __webpack_require__(/*! ./on.js */ \"./node_modules/socket.io-client/build/cjs/on.js\");\nconst component_emitter_1 = __webpack_require__(/*! @socket.io/component-emitter */ \"./node_modules/@socket.io/component-emitter/index.mjs\");\nconst debug_1 = __importDefault(__webpack_require__(/*! debug */ \"./node_modules/socket.io-client/node_modules/debug/src/browser.js\")); // debug()\nconst debug = debug_1.default(\"socket.io-client:socket\"); // debug()\n/**\n * Internal events.\n * These events can't be emitted by the user.\n */\nconst RESERVED_EVENTS = Object.freeze({\n connect: 1,\n connect_error: 1,\n disconnect: 1,\n disconnecting: 1,\n // EventEmitter reserved events: https://nodejs.org/api/events.html#events_event_newlistener\n newListener: 1,\n removeListener: 1,\n});\n/**\n * A Socket is the fundamental class for interacting with the server.\n *\n * A Socket belongs to a certain Namespace (by default /) and uses an underlying {@link Manager} to communicate.\n *\n * @example\n * const socket = io();\n *\n * socket.on(\"connect\", () => {\n * console.log(\"connected\");\n * });\n *\n * // send an event to the server\n * socket.emit(\"foo\", \"bar\");\n *\n * socket.on(\"foobar\", () => {\n * // an event was received from the server\n * });\n *\n * // upon disconnection\n * socket.on(\"disconnect\", (reason) => {\n * console.log(`disconnected due to ${reason}`);\n * });\n */\nclass Socket extends component_emitter_1.Emitter {\n /**\n * `Socket` constructor.\n */\n constructor(io, nsp, opts) {\n super();\n /**\n * Whether the socket is currently connected to the server.\n *\n * @example\n * const socket = io();\n *\n * socket.on(\"connect\", () => {\n * console.log(socket.connected); // true\n * });\n *\n * socket.on(\"disconnect\", () => {\n * console.log(socket.connected); // false\n * });\n */\n this.connected = false;\n /**\n * Buffer for packets received before the CONNECT packet\n */\n this.receiveBuffer = [];\n /**\n * Buffer for packets that will be sent once the socket is connected\n */\n this.sendBuffer = [];\n this.ids = 0;\n this.acks = {};\n this.flags = {};\n this.io = io;\n this.nsp = nsp;\n if (opts && opts.auth) {\n this.auth = opts.auth;\n }\n if (this.io._autoConnect)\n this.open();\n }\n /**\n * Whether the socket is currently disconnected\n *\n * @example\n * const socket = io();\n *\n * socket.on(\"connect\", () => {\n * console.log(socket.disconnected); // false\n * });\n *\n * socket.on(\"disconnect\", () => {\n * console.log(socket.disconnected); // true\n * });\n */\n get disconnected() {\n return !this.connected;\n }\n /**\n * Subscribe to open, close and packet events\n *\n * @private\n */\n subEvents() {\n if (this.subs)\n return;\n const io = this.io;\n this.subs = [\n on_js_1.on(io, \"open\", this.onopen.bind(this)),\n on_js_1.on(io, \"packet\", this.onpacket.bind(this)),\n on_js_1.on(io, \"error\", this.onerror.bind(this)),\n on_js_1.on(io, \"close\", this.onclose.bind(this)),\n ];\n }\n /**\n * Whether the Socket will try to reconnect when its Manager connects or reconnects.\n *\n * @example\n * const socket = io();\n *\n * console.log(socket.active); // true\n *\n * socket.on(\"disconnect\", (reason) => {\n * if (reason === \"io server disconnect\") {\n * // the disconnection was initiated by the server, you need to manually reconnect\n * console.log(socket.active); // false\n * }\n * // else the socket will automatically try to reconnect\n * console.log(socket.active); // true\n * });\n */\n get active() {\n return !!this.subs;\n }\n /**\n * \"Opens\" the socket.\n *\n * @example\n * const socket = io({\n * autoConnect: false\n * });\n *\n * socket.connect();\n */\n connect() {\n if (this.connected)\n return this;\n this.subEvents();\n if (!this.io[\"_reconnecting\"])\n this.io.open(); // ensure open\n if (\"open\" === this.io._readyState)\n this.onopen();\n return this;\n }\n /**\n * Alias for {@link connect()}.\n */\n open() {\n return this.connect();\n }\n /**\n * Sends a `message` event.\n *\n * This method mimics the WebSocket.send() method.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send\n *\n * @example\n * socket.send(\"hello\");\n *\n * // this is equivalent to\n * socket.emit(\"message\", \"hello\");\n *\n * @return self\n */\n send(...args) {\n args.unshift(\"message\");\n this.emit.apply(this, args);\n return this;\n }\n /**\n * Override `emit`.\n * If the event is in `events`, it's emitted normally.\n *\n * @example\n * socket.emit(\"hello\", \"world\");\n *\n * // all serializable datastructures are supported (no need to call JSON.stringify)\n * socket.emit(\"hello\", 1, \"2\", { 3: [\"4\"], 5: Uint8Array.from([6]) });\n *\n * // with an acknowledgement from the server\n * socket.emit(\"hello\", \"world\", (val) => {\n * // ...\n * });\n *\n * @return self\n */\n emit(ev, ...args) {\n if (RESERVED_EVENTS.hasOwnProperty(ev)) {\n throw new Error('\"' + ev.toString() + '\" is a reserved event name');\n }\n args.unshift(ev);\n const packet = {\n type: socket_io_parser_1.PacketType.EVENT,\n data: args,\n };\n packet.options = {};\n packet.options.compress = this.flags.compress !== false;\n // event ack callback\n if (\"function\" === typeof args[args.length - 1]) {\n const id = this.ids++;\n debug(\"emitting packet with ack id %d\", id);\n const ack = args.pop();\n this._registerAckCallback(id, ack);\n packet.id = id;\n }\n const isTransportWritable = this.io.engine &&\n this.io.engine.transport &&\n this.io.engine.transport.writable;\n const discardPacket = this.flags.volatile && (!isTransportWritable || !this.connected);\n if (discardPacket) {\n debug(\"discard packet as the transport is not currently writable\");\n }\n else if (this.connected) {\n this.notifyOutgoingListeners(packet);\n this.packet(packet);\n }\n else {\n this.sendBuffer.push(packet);\n }\n this.flags = {};\n return this;\n }\n /**\n * @private\n */\n _registerAckCallback(id, ack) {\n const timeout = this.flags.timeout;\n if (timeout === undefined) {\n this.acks[id] = ack;\n return;\n }\n // @ts-ignore\n const timer = this.io.setTimeoutFn(() => {\n delete this.acks[id];\n for (let i = 0; i < this.sendBuffer.length; i++) {\n if (this.sendBuffer[i].id === id) {\n debug(\"removing packet with ack id %d from the buffer\", id);\n this.sendBuffer.splice(i, 1);\n }\n }\n debug(\"event with ack id %d has timed out after %d ms\", id, timeout);\n ack.call(this, new Error(\"operation has timed out\"));\n }, timeout);\n this.acks[id] = (...args) => {\n // @ts-ignore\n this.io.clearTimeoutFn(timer);\n ack.apply(this, [null, ...args]);\n };\n }\n /**\n * Sends a packet.\n *\n * @param packet\n * @private\n */\n packet(packet) {\n packet.nsp = this.nsp;\n this.io._packet(packet);\n }\n /**\n * Called upon engine `open`.\n *\n * @private\n */\n onopen() {\n debug(\"transport is open - connecting\");\n if (typeof this.auth == \"function\") {\n this.auth((data) => {\n this.packet({ type: socket_io_parser_1.PacketType.CONNECT, data });\n });\n }\n else {\n this.packet({ type: socket_io_parser_1.PacketType.CONNECT, data: this.auth });\n }\n }\n /**\n * Called upon engine or manager `error`.\n *\n * @param err\n * @private\n */\n onerror(err) {\n if (!this.connected) {\n this.emitReserved(\"connect_error\", err);\n }\n }\n /**\n * Called upon engine `close`.\n *\n * @param reason\n * @param description\n * @private\n */\n onclose(reason, description) {\n debug(\"close (%s)\", reason);\n this.connected = false;\n delete this.id;\n this.emitReserved(\"disconnect\", reason, description);\n }\n /**\n * Called with socket packet.\n *\n * @param packet\n * @private\n */\n onpacket(packet) {\n const sameNamespace = packet.nsp === this.nsp;\n if (!sameNamespace)\n return;\n switch (packet.type) {\n case socket_io_parser_1.PacketType.CONNECT:\n if (packet.data && packet.data.sid) {\n const id = packet.data.sid;\n this.onconnect(id);\n }\n else {\n this.emitReserved(\"connect_error\", new Error(\"It seems you are trying to reach a Socket.IO server in v2.x with a v3.x client, but they are not compatible (more information here: https://socket.io/docs/v3/migrating-from-2-x-to-3-0/)\"));\n }\n break;\n case socket_io_parser_1.PacketType.EVENT:\n case socket_io_parser_1.PacketType.BINARY_EVENT:\n this.onevent(packet);\n break;\n case socket_io_parser_1.PacketType.ACK:\n case socket_io_parser_1.PacketType.BINARY_ACK:\n this.onack(packet);\n break;\n case socket_io_parser_1.PacketType.DISCONNECT:\n this.ondisconnect();\n break;\n case socket_io_parser_1.PacketType.CONNECT_ERROR:\n this.destroy();\n const err = new Error(packet.data.message);\n // @ts-ignore\n err.data = packet.data.data;\n this.emitReserved(\"connect_error\", err);\n break;\n }\n }\n /**\n * Called upon a server event.\n *\n * @param packet\n * @private\n */\n onevent(packet) {\n const args = packet.data || [];\n debug(\"emitting event %j\", args);\n if (null != packet.id) {\n debug(\"attaching ack callback to event\");\n args.push(this.ack(packet.id));\n }\n if (this.connected) {\n this.emitEvent(args);\n }\n else {\n this.receiveBuffer.push(Object.freeze(args));\n }\n }\n emitEvent(args) {\n if (this._anyListeners && this._anyListeners.length) {\n const listeners = this._anyListeners.slice();\n for (const listener of listeners) {\n listener.apply(this, args);\n }\n }\n super.emit.apply(this, args);\n }\n /**\n * Produces an ack callback to emit with an event.\n *\n * @private\n */\n ack(id) {\n const self = this;\n let sent = false;\n return function (...args) {\n // prevent double callbacks\n if (sent)\n return;\n sent = true;\n debug(\"sending ack %j\", args);\n self.packet({\n type: socket_io_parser_1.PacketType.ACK,\n id: id,\n data: args,\n });\n };\n }\n /**\n * Called upon a server acknowlegement.\n *\n * @param packet\n * @private\n */\n onack(packet) {\n const ack = this.acks[packet.id];\n if (\"function\" === typeof ack) {\n debug(\"calling ack %s with %j\", packet.id, packet.data);\n ack.apply(this, packet.data);\n delete this.acks[packet.id];\n }\n else {\n debug(\"bad ack %s\", packet.id);\n }\n }\n /**\n * Called upon server connect.\n *\n * @private\n */\n onconnect(id) {\n debug(\"socket connected with id %s\", id);\n this.id = id;\n this.connected = true;\n this.emitBuffered();\n this.emitReserved(\"connect\");\n }\n /**\n * Emit buffered events (received and emitted).\n *\n * @private\n */\n emitBuffered() {\n this.receiveBuffer.forEach((args) => this.emitEvent(args));\n this.receiveBuffer = [];\n this.sendBuffer.forEach((packet) => {\n this.notifyOutgoingListeners(packet);\n this.packet(packet);\n });\n this.sendBuffer = [];\n }\n /**\n * Called upon server disconnect.\n *\n * @private\n */\n ondisconnect() {\n debug(\"server disconnect (%s)\", this.nsp);\n this.destroy();\n this.onclose(\"io server disconnect\");\n }\n /**\n * Called upon forced client/server side disconnections,\n * this method ensures the manager stops tracking us and\n * that reconnections don't get triggered for this.\n *\n * @private\n */\n destroy() {\n if (this.subs) {\n // clean subscriptions to avoid reconnections\n this.subs.forEach((subDestroy) => subDestroy());\n this.subs = undefined;\n }\n this.io[\"_destroy\"](this);\n }\n /**\n * Disconnects the socket manually. In that case, the socket will not try to reconnect.\n *\n * If this is the last active Socket instance of the {@link Manager}, the low-level connection will be closed.\n *\n * @example\n * const socket = io();\n *\n * socket.on(\"disconnect\", (reason) => {\n * // console.log(reason); prints \"io client disconnect\"\n * });\n *\n * socket.disconnect();\n *\n * @return self\n */\n disconnect() {\n if (this.connected) {\n debug(\"performing disconnect (%s)\", this.nsp);\n this.packet({ type: socket_io_parser_1.PacketType.DISCONNECT });\n }\n // remove socket from pool\n this.destroy();\n if (this.connected) {\n // fire events\n this.onclose(\"io client disconnect\");\n }\n return this;\n }\n /**\n * Alias for {@link disconnect()}.\n *\n * @return self\n */\n close() {\n return this.disconnect();\n }\n /**\n * Sets the compress flag.\n *\n * @example\n * socket.compress(false).emit(\"hello\");\n *\n * @param compress - if `true`, compresses the sending data\n * @return self\n */\n compress(compress) {\n this.flags.compress = compress;\n return this;\n }\n /**\n * Sets a modifier for a subsequent event emission that the event message will be dropped when this socket is not\n * ready to send messages.\n *\n * @example\n * socket.volatile.emit(\"hello\"); // the server may or may not receive it\n *\n * @returns self\n */\n get volatile() {\n this.flags.volatile = true;\n return this;\n }\n /**\n * Sets a modifier for a subsequent event emission that the callback will be called with an error when the\n * given number of milliseconds have elapsed without an acknowledgement from the server:\n *\n * @example\n * socket.timeout(5000).emit(\"my-event\", (err) => {\n * if (err) {\n * // the server did not acknowledge the event in the given delay\n * }\n * });\n *\n * @returns self\n */\n timeout(timeout) {\n this.flags.timeout = timeout;\n return this;\n }\n /**\n * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the\n * callback.\n *\n * @example\n * socket.onAny((event, ...args) => {\n * console.log(`got ${event}`);\n * });\n *\n * @param listener\n */\n onAny(listener) {\n this._anyListeners = this._anyListeners || [];\n this._anyListeners.push(listener);\n return this;\n }\n /**\n * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the\n * callback. The listener is added to the beginning of the listeners array.\n *\n * @example\n * socket.prependAny((event, ...args) => {\n * console.log(`got event ${event}`);\n * });\n *\n * @param listener\n */\n prependAny(listener) {\n this._anyListeners = this._anyListeners || [];\n this._anyListeners.unshift(listener);\n return this;\n }\n /**\n * Removes the listener that will be fired when any event is emitted.\n *\n * @example\n * const catchAllListener = (event, ...args) => {\n * console.log(`got event ${event}`);\n * }\n *\n * socket.onAny(catchAllListener);\n *\n * // remove a specific listener\n * socket.offAny(catchAllListener);\n *\n * // or remove all listeners\n * socket.offAny();\n *\n * @param listener\n */\n offAny(listener) {\n if (!this._anyListeners) {\n return this;\n }\n if (listener) {\n const listeners = this._anyListeners;\n for (let i = 0; i < listeners.length; i++) {\n if (listener === listeners[i]) {\n listeners.splice(i, 1);\n return this;\n }\n }\n }\n else {\n this._anyListeners = [];\n }\n return this;\n }\n /**\n * Returns an array of listeners that are listening for any event that is specified. This array can be manipulated,\n * e.g. to remove listeners.\n */\n listenersAny() {\n return this._anyListeners || [];\n }\n /**\n * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the\n * callback.\n *\n * Note: acknowledgements sent to the server are not included.\n *\n * @example\n * socket.onAnyOutgoing((event, ...args) => {\n * console.log(`sent event ${event}`);\n * });\n *\n * @param listener\n */\n onAnyOutgoing(listener) {\n this._anyOutgoingListeners = this._anyOutgoingListeners || [];\n this._anyOutgoingListeners.push(listener);\n return this;\n }\n /**\n * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the\n * callback. The listener is added to the beginning of the listeners array.\n *\n * Note: acknowledgements sent to the server are not included.\n *\n * @example\n * socket.prependAnyOutgoing((event, ...args) => {\n * console.log(`sent event ${event}`);\n * });\n *\n * @param listener\n */\n prependAnyOutgoing(listener) {\n this._anyOutgoingListeners = this._anyOutgoingListeners || [];\n this._anyOutgoingListeners.unshift(listener);\n return this;\n }\n /**\n * Removes the listener that will be fired when any event is emitted.\n *\n * @example\n * const catchAllListener = (event, ...args) => {\n * console.log(`sent event ${event}`);\n * }\n *\n * socket.onAnyOutgoing(catchAllListener);\n *\n * // remove a specific listener\n * socket.offAnyOutgoing(catchAllListener);\n *\n * // or remove all listeners\n * socket.offAnyOutgoing();\n *\n * @param [listener] - the catch-all listener (optional)\n */\n offAnyOutgoing(listener) {\n if (!this._anyOutgoingListeners) {\n return this;\n }\n if (listener) {\n const listeners = this._anyOutgoingListeners;\n for (let i = 0; i < listeners.length; i++) {\n if (listener === listeners[i]) {\n listeners.splice(i, 1);\n return this;\n }\n }\n }\n else {\n this._anyOutgoingListeners = [];\n }\n return this;\n }\n /**\n * Returns an array of listeners that are listening for any event that is specified. This array can be manipulated,\n * e.g. to remove listeners.\n */\n listenersAnyOutgoing() {\n return this._anyOutgoingListeners || [];\n }\n /**\n * Notify the listeners for each packet sent\n *\n * @param packet\n *\n * @private\n */\n notifyOutgoingListeners(packet) {\n if (this._anyOutgoingListeners && this._anyOutgoingListeners.length) {\n const listeners = this._anyOutgoingListeners.slice();\n for (const listener of listeners) {\n listener.apply(this, packet.data);\n }\n }\n }\n}\nexports.Socket = Socket;\n\n\n//# sourceURL=webpack://dcp/./node_modules/socket.io-client/build/cjs/socket.js?");
|
|
5675
5792
|
|
|
5676
5793
|
/***/ }),
|
|
5677
5794
|
|
|
@@ -5682,7 +5799,7 @@ eval("\nvar __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5682
5799
|
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
5683
5800
|
|
|
5684
5801
|
"use strict";
|
|
5685
|
-
eval("\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.url = void 0;\nconst engine_io_client_1 = __webpack_require__(/*! engine.io-client */ \"./node_modules/engine.io-client/build/cjs/index.js\");\nconst debug_1 = __importDefault(__webpack_require__(/*! debug */ \"./node_modules/debug/src/browser.js\")); // debug()\nconst debug = debug_1.default(\"socket.io-client:url\"); // debug()\n/**\n * URL parser.\n *\n * @param uri - url\n * @param path - the request path of the connection\n * @param loc - An object meant to mimic window.location.\n * Defaults to window.location.\n * @public\n */\nfunction url(uri, path = \"\", loc) {\n let obj = uri;\n // default to window.location\n loc = loc || (typeof location !== \"undefined\" && location);\n if (null == uri)\n uri = loc.protocol + \"//\" + loc.host;\n // relative path support\n if (typeof uri === \"string\") {\n if (\"/\" === uri.charAt(0)) {\n if (\"/\" === uri.charAt(1)) {\n uri = loc.protocol + uri;\n }\n else {\n uri = loc.host + uri;\n }\n }\n if (!/^(https?|wss?):\\/\\//.test(uri)) {\n debug(\"protocol-less url %s\", uri);\n if (\"undefined\" !== typeof loc) {\n uri = loc.protocol + \"//\" + uri;\n }\n else {\n uri = \"https://\" + uri;\n }\n }\n // parse\n debug(\"parse %s\", uri);\n obj = engine_io_client_1.parse(uri);\n }\n // make sure we treat `localhost:80` and `localhost` equally\n if (!obj.port) {\n if (/^(http|ws)$/.test(obj.protocol)) {\n obj.port = \"80\";\n }\n else if (/^(http|ws)s$/.test(obj.protocol)) {\n obj.port = \"443\";\n }\n }\n obj.path = obj.path || \"/\";\n const ipv6 = obj.host.indexOf(\":\") !== -1;\n const host = ipv6 ? \"[\" + obj.host + \"]\" : obj.host;\n // define unique id\n obj.id = obj.protocol + \"://\" + host + \":\" + obj.port + path;\n // define href\n obj.href =\n obj.protocol +\n \"://\" +\n host +\n (loc && loc.port === obj.port ? \"\" : \":\" + obj.port);\n return obj;\n}\nexports.url = url;\n\n\n//# sourceURL=webpack://dcp/./node_modules/socket.io-client/build/cjs/url.js?");
|
|
5802
|
+
eval("\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.url = void 0;\nconst engine_io_client_1 = __webpack_require__(/*! engine.io-client */ \"./node_modules/engine.io-client/build/cjs/index.js\");\nconst debug_1 = __importDefault(__webpack_require__(/*! debug */ \"./node_modules/socket.io-client/node_modules/debug/src/browser.js\")); // debug()\nconst debug = debug_1.default(\"socket.io-client:url\"); // debug()\n/**\n * URL parser.\n *\n * @param uri - url\n * @param path - the request path of the connection\n * @param loc - An object meant to mimic window.location.\n * Defaults to window.location.\n * @public\n */\nfunction url(uri, path = \"\", loc) {\n let obj = uri;\n // default to window.location\n loc = loc || (typeof location !== \"undefined\" && location);\n if (null == uri)\n uri = loc.protocol + \"//\" + loc.host;\n // relative path support\n if (typeof uri === \"string\") {\n if (\"/\" === uri.charAt(0)) {\n if (\"/\" === uri.charAt(1)) {\n uri = loc.protocol + uri;\n }\n else {\n uri = loc.host + uri;\n }\n }\n if (!/^(https?|wss?):\\/\\//.test(uri)) {\n debug(\"protocol-less url %s\", uri);\n if (\"undefined\" !== typeof loc) {\n uri = loc.protocol + \"//\" + uri;\n }\n else {\n uri = \"https://\" + uri;\n }\n }\n // parse\n debug(\"parse %s\", uri);\n obj = engine_io_client_1.parse(uri);\n }\n // make sure we treat `localhost:80` and `localhost` equally\n if (!obj.port) {\n if (/^(http|ws)$/.test(obj.protocol)) {\n obj.port = \"80\";\n }\n else if (/^(http|ws)s$/.test(obj.protocol)) {\n obj.port = \"443\";\n }\n }\n obj.path = obj.path || \"/\";\n const ipv6 = obj.host.indexOf(\":\") !== -1;\n const host = ipv6 ? \"[\" + obj.host + \"]\" : obj.host;\n // define unique id\n obj.id = obj.protocol + \"://\" + host + \":\" + obj.port + path;\n // define href\n obj.href =\n obj.protocol +\n \"://\" +\n host +\n (loc && loc.port === obj.port ? \"\" : \":\" + obj.port);\n return obj;\n}\nexports.url = url;\n\n\n//# sourceURL=webpack://dcp/./node_modules/socket.io-client/build/cjs/url.js?");
|
|
5686
5803
|
|
|
5687
5804
|
/***/ }),
|
|
5688
5805
|
|