diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..611000fae --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "unitTests", + "args": ["${workspaceFolder}/tests/unitTests/src/index.ts"], + "runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"], + "outputCapture": "std" + } + ] +} diff --git a/package-lock.json b/package-lock.json index cc4b9ccd6..5778e40a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "blockstack", - "version": "19.3.0", + "version": "19.4.0-beta.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -985,6 +985,25 @@ "@types/node": "*" } }, + "@types/jsdom": { + "version": "12.2.4", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-12.2.4.tgz", + "integrity": "sha512-q+De3S/Ri6U9uPx89YA1XuC+QIBgndIfvBaaJG0pRT8Oqa75k4Mr7G9CRZjIvlbLGIukO/31DFGFJYlQBmXf/A==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^4.0.0" + }, + "dependencies": { + "parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true + } + } + }, "@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -1032,6 +1051,12 @@ "@types/node": "*" } }, + "@types/tough-cookie": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz", + "integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==", + "dev": true + }, "@types/triplesec": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/triplesec/-/triplesec-3.0.0.tgz", @@ -1270,6 +1295,12 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "abab": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", + "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==", + "dev": true + }, "accepts": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", @@ -1292,6 +1323,16 @@ "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==", "dev": true }, + "acorn-globals": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.3.tgz", + "integrity": "sha512-vkR40VwS2SYO98AIeFvzWWh+xyc2qi9s7OoXSFEGIP/rOJKzjnhykaZJNnHdoq4BL2gGxI5EZOU16z896EYnOQ==", + "dev": true, + "requires": { + "acorn": "^6.0.1", + "acorn-walk": "^6.0.1" + } + }, "acorn-jsx": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", @@ -1436,6 +1477,12 @@ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, + "array-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", + "dev": true + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -1886,6 +1933,12 @@ "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" }, + "browser-process-hrtime": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", + "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", + "dev": true + }, "browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", @@ -2522,19 +2575,12 @@ } }, "cross-fetch": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-2.2.3.tgz", - "integrity": "sha512-PrWWNH3yL2NYIb/7WF/5vFG3DCQiXDOVf8k3ijatbrtnwNuhMWLC7YF7uqf53tbTFDzHIUD8oITw4Bxt8ST3Nw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.4.tgz", + "integrity": "sha512-MSHgpjQqgbT/94D4CyADeNoYh52zMkCX4pcJvPP5WqPsLFMKjr2TCMg381ox5qI0ii2dPwaLx/00477knXqXVw==", "requires": { - "node-fetch": "2.1.2", - "whatwg-fetch": "2.0.4" - }, - "dependencies": { - "node-fetch": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz", - "integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=" - } + "node-fetch": "2.6.0", + "whatwg-fetch": "3.0.0" } }, "cross-spawn": { @@ -2585,6 +2631,21 @@ "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" }, + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + }, + "cssstyle": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz", + "integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==", + "dev": true, + "requires": { + "cssom": "0.3.x" + } + }, "cyclist": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", @@ -2599,6 +2660,17 @@ "assert-plus": "^1.0.0" } }, + "data-urls": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", + "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "whatwg-mimetype": "^2.2.0", + "whatwg-url": "^7.0.0" + } + }, "date-now": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", @@ -2793,6 +2865,15 @@ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" }, + "domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "dev": true, + "requires": { + "webidl-conversions": "^4.0.2" + } + }, "domhandler": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", @@ -3037,6 +3118,27 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, + "escodegen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.1.tgz", + "integrity": "sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==", + "dev": true, + "requires": { + "esprima": "^3.1.3", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "dev": true + } + } + }, "eslint": { "version": "5.16.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", @@ -3696,6 +3798,16 @@ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", "dev": true }, + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "dev": true, + "requires": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + }, "path-to-regexp": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", @@ -4100,7 +4212,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -4121,12 +4234,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4141,17 +4256,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -4268,7 +4386,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -4280,6 +4399,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4294,6 +4414,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -4301,12 +4422,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -4325,6 +4448,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -4405,7 +4529,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -4417,6 +4542,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -4502,7 +4628,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -4538,6 +4665,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -4557,6 +4685,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -4600,12 +4729,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -4903,6 +5034,15 @@ "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", "dev": true }, + "html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "dev": true, + "requires": { + "whatwg-encoding": "^1.0.1" + } + }, "htmlparser2": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", @@ -5137,6 +5277,12 @@ "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, "ipaddr.js": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", @@ -5503,6 +5649,62 @@ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, + "jsdom": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-15.1.1.tgz", + "integrity": "sha512-cQZRBB33arrDAeCrAEWn1U3SvrvC8XysBua9Oqg1yWrsY/gYcusloJC3RZJXuY5eehSCmws8f2YeliCqGSkrtQ==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "acorn": "^6.1.1", + "acorn-globals": "^4.3.2", + "array-equal": "^1.0.0", + "cssom": "^0.3.6", + "cssstyle": "^1.2.2", + "data-urls": "^1.1.0", + "domexception": "^1.0.1", + "escodegen": "^1.11.1", + "html-encoding-sniffer": "^1.0.2", + "nwsapi": "^2.1.4", + "parse5": "5.1.0", + "pn": "^1.1.0", + "request": "^2.88.0", + "request-promise-native": "^1.0.7", + "saxes": "^3.1.9", + "symbol-tree": "^3.2.2", + "tough-cookie": "^3.0.1", + "w3c-hr-time": "^1.0.1", + "w3c-xmlserializer": "^1.1.2", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^7.0.0", + "ws": "^7.0.0", + "xml-name-validator": "^3.0.0" + }, + "dependencies": { + "tough-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", + "dev": true, + "requires": { + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "ws": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.1.2.tgz", + "integrity": "sha512-gftXq3XI81cJCgkUiAVixA0raD9IVmXqsylCrjRygw4+UOOGzPoxnQ6r/CnVL9i+mDncJo94tSkyrtuuQVBmrg==", + "dev": true, + "requires": { + "async-limiter": "^1.0.0" + } + } + } + }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -5810,6 +6012,12 @@ "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=" }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, "lodash.unescape": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz", @@ -6282,14 +6490,9 @@ } }, "node-fetch": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", - "dev": true, - "requires": { - "encoding": "^0.1.11", - "is-stream": "^1.0.1" - } + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" }, "node-libs-browser": { "version": "2.2.0", @@ -6418,6 +6621,12 @@ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, + "nwsapi": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.4.tgz", + "integrity": "sha512-iGfd9Y6SFdTNldEy2L0GUhcarIutFmk+MPWIn9dmj8NMIup03G08uUF2KGbbmv/Ux4RT0VZJoP/sVbWA6d/VIw==", + "dev": true + }, "nyc": { "version": "14.1.1", "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.1.tgz", @@ -6950,6 +7159,12 @@ "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", "dev": true }, + "parse5": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", + "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", + "dev": true + }, "parseurl": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", @@ -7083,6 +7298,12 @@ "find-up": "^3.0.0" } }, + "pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", + "dev": true + }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -7521,6 +7742,26 @@ "uuid": "^3.3.2" } }, + "request-promise-core": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", + "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + }, + "request-promise-native": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", + "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", + "dev": true, + "requires": { + "request-promise-core": "1.1.2", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -7676,6 +7917,15 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "saxes": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.11.tgz", + "integrity": "sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g==", + "dev": true, + "requires": { + "xmlchars": "^2.1.1" + } + }, "schema-inspector": { "version": "1.6.8", "resolved": "https://registry.npmjs.org/schema-inspector/-/schema-inspector-1.6.8.tgz", @@ -8179,6 +8429,12 @@ "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", "dev": true }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true + }, "stream-browserify": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", @@ -8340,6 +8596,12 @@ "has-flag": "^3.0.0" } }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, "table": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/table/-/table-5.2.3.tgz", @@ -8643,6 +8905,15 @@ } } }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, "trim-right": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", @@ -8808,6 +9079,12 @@ "typescript": "3.5.x" }, "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -8840,9 +9117,9 @@ "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" }, "typescript": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.2.tgz", - "integrity": "sha512-Og2Vn6Mk7JAuWA1hQdDQN/Ekm/SchX80VzLhjKN9ETYrIepBFAd8PkOdOTK2nKt0FCkmMZKBJvQ1dV1gIxPu/A==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", + "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", "dev": true }, "uglify-js": { @@ -9104,6 +9381,26 @@ "indexof": "0.0.1" } }, + "w3c-hr-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", + "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", + "dev": true, + "requires": { + "browser-process-hrtime": "^0.1.2" + } + }, + "w3c-xmlserializer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz", + "integrity": "sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==", + "dev": true, + "requires": { + "domexception": "^1.0.1", + "webidl-conversions": "^4.0.2", + "xml-name-validator": "^3.0.0" + } + }, "watchpack": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", @@ -9115,6 +9412,12 @@ "neo-async": "^2.5.0" } }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, "webpack": { "version": "4.29.6", "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.29.6.tgz", @@ -9226,10 +9529,36 @@ "source-map": "~0.6.1" } }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "requires": { + "iconv-lite": "0.4.24" + } + }, "whatwg-fetch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", - "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", + "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "whatwg-url": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", + "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } }, "which": { "version": "1.3.1", @@ -9351,6 +9680,18 @@ "async-limiter": "~1.0.0" } }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "xmlchars": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.1.1.tgz", + "integrity": "sha512-7hew1RPJ1iIuje/Y01bGD/mXokXxegAgVS+e+E0wSi2ILHQkYAH1+JXARwTjZSM4Z4Z+c73aKspEcqj+zPPL/w==", + "dev": true + }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", diff --git a/package.json b/package.json index 1095496c8..191c270f5 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "@types/cheerio": "^0.22.10", "@types/fetch-mock": "^5.8.0", "@types/form-data": "^2.2.1", + "@types/jsdom": "^12.2.4", "@types/node": "^10", "@types/proxyquire": "^1.3.28", "@types/ripemd160": "^2.0.0", @@ -107,8 +108,8 @@ "eslint-plugin-import": "^2.16.0", "express": "^4.16.4", "fetch-mock": "^5.5.0", + "jsdom": "^15.1.1", "mock-local-storage": "^1.1.8", - "node-fetch": "^1.6.3", "nyc": "^14.1.1", "opn": "^4.0.2", "proxyquire": "^2.1.0", @@ -121,7 +122,7 @@ "ts-loader": "^5.3.3", "ts-node": "^8.0.2", "typedoc": "^0.15.0", - "typescript": "^3.3.3333", + "typescript": "^3.5.3", "webpack": "^4.29.6", "webpack-assets-manifest": "^3.1.1", "webpack-bundle-analyzer": "^3.4.1", @@ -135,7 +136,7 @@ "bitcoinjs-lib": "^5.1.2", "bn.js": "^4.11.8", "cheerio": "^0.22.0", - "cross-fetch": "^2.2.2", + "cross-fetch": "^3.0.4", "elliptic": "^6.4.1", "form-data": "^2.3.3", "jsontokens": "^2.0.2", diff --git a/src/auth/userSession.ts b/src/auth/userSession.ts index e28d0acaf..73fafde9b 100644 --- a/src/auth/userSession.ts +++ b/src/auth/userSession.ts @@ -294,7 +294,11 @@ export class UserSession { * @returns {Promise} that resolves if the operation succeed and rejects * if it failed */ - putFile(path: string, content: string | Buffer, options?: import('../storage').PutFileOptions) { + putFile( + path: string, + content: string | Buffer | ArrayBufferView | Blob, + options?: import('../storage').PutFileOptions + ) { return storage.putFile(path, content, options, this) } diff --git a/src/fetchUtil.ts b/src/fetchUtil.ts index 5a08121d3..be48b809b 100644 --- a/src/fetchUtil.ts +++ b/src/fetchUtil.ts @@ -1,7 +1,9 @@ +import 'cross-fetch/polyfill' /** @ignore */ -export function fetchPrivate(input: RequestInfo, init?: RequestInit): Promise { - init = init || { } - init.referrerPolicy = 'no-referrer' - return fetch(input, init) +export async function fetchPrivate(input: RequestInfo, init?: RequestInit): Promise { + const fetchOpts = init || { } + fetchOpts.referrerPolicy = 'no-referrer' + const fetchResult = await fetch(input, fetchOpts) + return fetchResult } diff --git a/src/storage/hub.ts b/src/storage/hub.ts index 2884a99da..16fc572a1 100644 --- a/src/storage/hub.ts +++ b/src/storage/hub.ts @@ -35,7 +35,8 @@ export interface GaiaHubConfig { * @ignore */ export async function uploadToGaiaHub( - filename: string, contents: any, + filename: string, + contents: Blob | Buffer | ArrayBufferView | string, hubConfig: GaiaHubConfig, contentType: string = 'application/octet-stream' ): Promise { @@ -63,7 +64,7 @@ export async function deleteFromGaiaHub( hubConfig: GaiaHubConfig ): Promise { Logger.debug(`deleteFromGaiaHub: deleting ${filename} from ${hubConfig.server}`) - const response = await fetch( + const response = await fetchPrivate( `${hubConfig.server}/delete/${hubConfig.address}/${filename}`, { method: 'DELETE', headers: { diff --git a/src/storage/index.ts b/src/storage/index.ts index 438031249..4200426b4 100644 --- a/src/storage/index.ts +++ b/src/storage/index.ts @@ -468,6 +468,61 @@ export function getFile( }) } +/** @ignore */ +type PutFileContent = string | Buffer | ArrayBufferView | Blob + +/** @ignore */ +class FileContentLoader { + content: PutFileContent + + loadedData?: Promise + + constructor(content: PutFileContent) { + this.content = content + } + + getContentType(): string { + if (typeof this.content === 'string') { + return 'text/plain; charset=utf-8' + } else if (typeof Blob !== 'undefined' && this.content instanceof Blob && this.content.type) { + return this.content.type + } else { + return 'application/octet-stream' + } + } + + private async loadContent(): Promise { + if (typeof this.content === 'string') { + return this.content + } else if (ArrayBuffer.isView(this.content)) { + return Buffer.from(this.content.buffer) + } else if (typeof Blob !== 'undefined' && this.content instanceof Blob) { + const reader = new FileReader() + const readPromise = new Promise((resolve, reject) => { + reader.onerror = (err) => { + reject(err) + } + reader.onload = () => { + const arrayBuffer = reader.result as ArrayBuffer + resolve(Buffer.from(arrayBuffer)) + } + reader.readAsArrayBuffer(this.content as Blob) + }) + const result = await readPromise + return result + } + const typeName = Object.prototype.toString.call(this.content) + throw new Error(`Unsupported content object type: ${typeName}`) + } + + load(): Promise { + if (this.loadedData === undefined) { + this.loadedData = this.loadContent() + } + return this.loadedData + } +} + /** * Stores the data provided in the app's data store to to the file specified. * @param {String} path - the path to store the data in @@ -477,10 +532,12 @@ export function getFile( */ export async function putFile( path: string, - content: string | Buffer, + content: string | Buffer | ArrayBufferView | Blob, options?: PutFileOptions, caller?: UserSession, ): Promise { + const contentLoader = new FileContentLoader(content) + const defaults: PutFileOptions = { encrypt: true, sign: false, @@ -491,7 +548,7 @@ export async function putFile( let { contentType } = opt if (!contentType) { - contentType = (typeof (content) === 'string') ? 'text/plain; charset=utf-8' : 'application/octet-stream' + contentType = contentLoader.getContentType() } if (!caller) { @@ -525,13 +582,14 @@ export async function putFile( // we perform two uploads. So the control-flow // here will return there. if (!opt.encrypt && opt.sign) { - const signatureObject = signECDSA(privateKey, content) + const contentData = await contentLoader.load() + const signatureObject = signECDSA(privateKey, contentData) const signatureContent = JSON.stringify(signatureObject) const gaiaHubConfig = await caller.getOrSetLocalGaiaHubConnection() try { const fileUrls = await Promise.all([ - uploadToGaiaHub(path, content, gaiaHubConfig, contentType), + uploadToGaiaHub(path, contentData, gaiaHubConfig, contentType), uploadToGaiaHub(`${path}${SIGNATURE_FILE_SUFFIX}`, signatureContent, gaiaHubConfig, 'application/json') ]) @@ -539,7 +597,7 @@ export async function putFile( } catch (error) { const freshHubConfig = await caller.setLocalGaiaHubConnection() const fileUrls = await Promise.all([ - uploadToGaiaHub(path, content, freshHubConfig, contentType), + uploadToGaiaHub(path, contentData, freshHubConfig, contentType), uploadToGaiaHub(`${path}${SIGNATURE_FILE_SUFFIX}`, signatureContent, freshHubConfig, 'application/json') ]) @@ -548,26 +606,31 @@ export async function putFile( } // In all other cases, we only need one upload. + let contentForUpload: Blob | Buffer | ArrayBufferView | string if (opt.encrypt && !opt.sign) { - content = encryptContent(content, { publicKey }) + const contentData = await contentLoader.load() + contentForUpload = encryptContent(contentData, { publicKey }) contentType = 'application/json' } else if (opt.encrypt && opt.sign) { - const cipherText = encryptContent(content, { publicKey }) + const contentData = await contentLoader.load() + const cipherText = encryptContent(contentData, { publicKey }) const signatureObject = signECDSA(privateKey, cipherText) const signedCipherObject = { signature: signatureObject.signature, publicKey: signatureObject.publicKey, cipherText } - content = JSON.stringify(signedCipherObject) + contentForUpload = JSON.stringify(signedCipherObject) contentType = 'application/json' + } else { + contentForUpload = content } const gaiaHubConfig = await caller.getOrSetLocalGaiaHubConnection() try { - return await uploadToGaiaHub(path, content, gaiaHubConfig, contentType) + return await uploadToGaiaHub(path, contentForUpload, gaiaHubConfig, contentType) } catch (error) { const freshHubConfig = await caller.setLocalGaiaHubConnection() - const file = await uploadToGaiaHub(path, content, freshHubConfig, contentType) + const file = await uploadToGaiaHub(path, contentForUpload, freshHubConfig, contentType) return file } } diff --git a/src/utils.ts b/src/utils.ts index 9a2a8d431..8e2fc2c94 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -252,7 +252,7 @@ interface GetGlobalObjectOptions { * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/self * @ignore */ -export function getGlobalObject( +export function getGlobalObject>( name: K, { throwIfUnavailable, usageDesc, returnEmptyObject }: GetGlobalObjectOptions = { } ): Window[K] { @@ -287,7 +287,7 @@ export function getGlobalObject( * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/self * @ignore */ -export function getGlobalObjects( +export function getGlobalObjects>( names: K[], { throwIfUnavailable, usageDesc, returnEmptyObject }: GetGlobalObjectOptions = {} ): Pick { diff --git a/tests/unitTests/src/unitTestsStorage.ts b/tests/unitTests/src/unitTestsStorage.ts index bb569d76e..2399bf8c6 100644 --- a/tests/unitTests/src/unitTestsStorage.ts +++ b/tests/unitTests/src/unitTestsStorage.ts @@ -1,4 +1,4 @@ -import * as test from 'tape' +import * as test from 'tape-promise/tape' import * as FetchMock from 'fetch-mock' import * as proxyquire from 'proxyquire' import * as sinon from 'sinon' @@ -13,6 +13,9 @@ import { getFile, getFileUrl, putFile, listFiles, deleteFile } from '../../../sr import { getPublicKeyFromPrivate } from '../../../src/keys' import { UserSession, AppConfig } from '../../../src' +import * as util from 'util' +import * as jsdom from 'jsdom' + // class LocalStorage { // constructor() { @@ -387,6 +390,244 @@ export function runStorageTests() { t.equal(content, deciphered) }) + test('putFile unencrypted, using Blob content', async (t) => { + const path = 'file.json' + const gaiaHubConfig = { + address: '1NZNxhoxobqwsNvTb16pdeiqvFvce3Yg8U', + server: 'https://hub.blockstack.org', + token: '', + url_prefix: 'gaia.testblockstack.org/hub/' + } + + const appConfig = new AppConfig(['store_write'], 'http://localhost:3000') + const blockstack = new UserSession({ appConfig }) + blockstack.store.getSessionData().userData = { + gaiaHubConfig + } + + const dom = new jsdom.JSDOM('', {}).window + const globalAPIs: {[key: string]: any } = { + File: dom.File, + Blob: dom.Blob, + FileReader: (dom as any).FileReader as FileReader + } + for (const globalAPI of Object.keys(globalAPIs)) { + (global as any)[globalAPI] = globalAPIs[globalAPI] + } + try { + const fileContent = new dom.File(['file content test'], 'filenametest.txt', { type: 'text/example' }) + const fullReadUrl = 'https://gaia.testblockstack.org/hub/1NZNxhoxobqwsNvTb16pdeiqvFvce3Yg8U/file.json' + + let uploadContentType: any + const uploadToGaiaHub = ( + filename: string, + contents: any, + hubConfig: any, + contentType: string) => { + uploadContentType = contentType + return Promise.resolve(fullReadUrl) + }; + + const { putFile } = proxyquire('../../../src/storage', { + './hub': { uploadToGaiaHub } + }) + + const options = { encrypt: false } + + const publicURL = await putFile(path, fileContent, options, blockstack) + t.equal(publicURL, fullReadUrl) + t.equal(uploadContentType, 'text/example') + } finally { + for (const globalAPI of Object.keys(globalAPIs)) { + delete (global as any)[globalAPI] + } + } + }) + + test('putFile encrypted, using Blob content, encrypted', async (t) => { + const dom = new jsdom.JSDOM('', {}).window + const globalAPIs: {[key: string]: any } = { + File: dom.File, + Blob: dom.Blob, + FileReader: (dom as any).FileReader as FileReader + } + for (const globalAPI of Object.keys(globalAPIs)) { + (global as any)[globalAPI] = globalAPIs[globalAPI] + } + try { + const contentDataString = 'file content test' + const fileContent = new dom.File([contentDataString], 'filenametest.txt', { type: 'text/example' }) + + const privateKey = 'a5c61c6ca7b3e7e55edee68566aeab22e4da26baa285c7bd10e8d2218aa3b229' + const appConfig = new AppConfig(['store_write'], 'http://localhost:3000') + const blockstack = new UserSession({ appConfig }) + + const path = 'file.json' + const gaiaHubConfig = { + address: '1NZNxhoxobqwsNvTb16pdeiqvFvce3Yg8U', + server: 'https://hub.blockstack.org', + token: '', + url_prefix: 'https://gaia.testblockstack.org/hub/' + } + + blockstack.store.getSessionData().userData = { + appPrivateKey: privateKey, + gaiaHubConfig + } // manually set private key for testing + + const fullReadUrl = 'https://gaia.testblockstack.org/hub/1NZNxhoxobqwsNvTb16pdeiqvFvce3Yg8A/file.json' + + let encryptedContent: any + const uploadToGaiaHub = ( + filename: string, + contents: any, + hubConfig: any, + contentType: string) => { + encryptedContent = contents + return Promise.resolve(fullReadUrl) + }; + const getFullReadUrl = sinon.stub().resolves(fullReadUrl) // eslint-disable-line no-shadow + + const { putFile } = proxyquire('../../../src/storage', { + './hub': { uploadToGaiaHub } + }) + const { getFile } = proxyquire('../../../src/storage', { // eslint-disable-line no-shadow + './hub': { getFullReadUrl } + }) + + const encryptOptions = { encrypt: true } + const decryptOptions = { decrypt: true } + // put and encrypt the file + const publicURL = await putFile(path, fileContent, encryptOptions, blockstack) + t.equal(publicURL, fullReadUrl) + FetchMock.get(fullReadUrl, encryptedContent) + const readContent = await getFile(path, decryptOptions, blockstack) + const readContentStr = readContent.toString() + t.equal(readContentStr, contentDataString) + } finally { + for (const globalAPI of Object.keys(globalAPIs)) { + delete (global as any)[globalAPI] + } + FetchMock.restore() + } + }) + + test('putFile unencrypted, using TypedArray content, encrypted', async (t) => { + try { + const contentDataString = 'file content test1234567' + const textEncoder = new util.TextEncoder() + const encodedArray = textEncoder.encode(contentDataString) + const fileContent = new Uint32Array(encodedArray.buffer) + + const privateKey = 'a5c61c6ca7b3e7e55edee68566aeab22e4da26baa285c7bd10e8d2218aa3b229' + const appConfig = new AppConfig(['store_write'], 'http://localhost:3000') + const blockstack = new UserSession({ appConfig }) + + const path = 'file.json' + const gaiaHubConfig = { + address: '1NZNxhoxobqwsNvTb16pdeiqvFvce3Yg8U', + server: 'https://hub.blockstack.org', + token: '', + url_prefix: 'https://gaia.testblockstack.org/hub/' + } + + blockstack.store.getSessionData().userData = { + appPrivateKey: privateKey, + gaiaHubConfig + } // manually set private key for testing + + const fullReadUrl = 'https://gaia.testblockstack.org/hub/1NZNxhoxobqwsNvTb16pdeiqvFvce3Yg8A/file.json' + + let postedContent: any + const uploadToGaiaHub = ( + filename: string, + contents: any, + hubConfig: any, + contentType: string) => { + postedContent = Buffer.from(contents.buffer).toString() + return Promise.resolve(fullReadUrl) + }; + const getFullReadUrl = sinon.stub().resolves(fullReadUrl) // eslint-disable-line no-shadow + + const { putFile } = proxyquire('../../../src/storage', { + './hub': { uploadToGaiaHub } + }) + const { getFile } = proxyquire('../../../src/storage', { // eslint-disable-line no-shadow + './hub': { getFullReadUrl } + }) + + const encryptOptions = { encrypt: false, contentType: 'text/plain; charset=utf-8' } + const decryptOptions = { decrypt: false } + // put and encrypt the file + const publicURL = await putFile(path, fileContent, encryptOptions, blockstack) + t.equal(publicURL, fullReadUrl) + FetchMock.get(fullReadUrl, { status: 200, body: postedContent, headers: {'Content-Type': 'text/plain; charset=utf-8' } }) + const readContent = await getFile(path, decryptOptions, blockstack) + const readContentStr = readContent.toString() + t.equal(readContentStr, contentDataString) + } finally { + FetchMock.restore() + } + }) + + test('putFile encrypted, using TypedArray content, encrypted', async (t) => { + try { + const contentDataString = 'file content test1234567' + const textEncoder = new util.TextEncoder() + const encodedArray = textEncoder.encode(contentDataString) + const fileContent = new Uint32Array(encodedArray.buffer) + + const privateKey = 'a5c61c6ca7b3e7e55edee68566aeab22e4da26baa285c7bd10e8d2218aa3b229' + const appConfig = new AppConfig(['store_write'], 'http://localhost:3000') + const blockstack = new UserSession({ appConfig }) + + const path = 'file.json' + const gaiaHubConfig = { + address: '1NZNxhoxobqwsNvTb16pdeiqvFvce3Yg8U', + server: 'https://hub.blockstack.org', + token: '', + url_prefix: 'https://gaia.testblockstack.org/hub/' + } + + blockstack.store.getSessionData().userData = { + appPrivateKey: privateKey, + gaiaHubConfig + } // manually set private key for testing + + const fullReadUrl = 'https://gaia.testblockstack.org/hub/1NZNxhoxobqwsNvTb16pdeiqvFvce3Yg8A/file.json' + + let encryptedContent: any + const uploadToGaiaHub = ( + filename: string, + contents: any, + hubConfig: any, + contentType: string) => { + encryptedContent = contents + return Promise.resolve(fullReadUrl) + }; + const getFullReadUrl = sinon.stub().resolves(fullReadUrl) // eslint-disable-line no-shadow + + const { putFile } = proxyquire('../../../src/storage', { + './hub': { uploadToGaiaHub } + }) + const { getFile } = proxyquire('../../../src/storage', { // eslint-disable-line no-shadow + './hub': { getFullReadUrl } + }) + + const encryptOptions = { encrypt: true } + const decryptOptions = { decrypt: true } + // put and encrypt the file + const publicURL = await putFile(path, fileContent, encryptOptions, blockstack) + t.equal(publicURL, fullReadUrl) + FetchMock.get(fullReadUrl, encryptedContent) + const readContent = await getFile(path, decryptOptions, blockstack) + const readContentStr = readContent.toString() + t.equal(readContentStr, contentDataString) + } finally { + FetchMock.restore() + } + }) + test('putFile unencrypted, not signed', (t) => { t.plan(1)