Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

skynet-js

Package Overview
Dependencies
Maintainers
2
Versions
68
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

skynet-js - npm Package Compare versions

Comparing version 3.0.2 to 4.0.0-beta

dist/client.d.ts

105

dist/client.js

@@ -18,2 +18,4 @@ "use strict";

var _file = require("./file");
var _skydb = require("./skydb");

@@ -25,2 +27,6 @@

var _mysky = require("./mysky");
var _utils = require("./mysky/utils");
/**

@@ -30,6 +36,10 @@ * The Skynet Client which can be used to access Skynet.

class SkynetClient {
// TODO: This is currently the url of the skapp and not the portal. It should be the value of 'skynet-portal-api' header. This will be a promise, which will be a breaking change.
// The initial portal URL, either given to `new SkynetClient()` or if not, the value of `defaultPortalUrl()`.
// The resolved API portal URL. The request won't be made until needed, or `initPortalUrl()` is called. The request is only made once, for all Skynet Clients.
// The given portal URL, if one was passed in to `new SkynetClient()`.
// Set methods (defined in other files).
// Upload
// Download
// MySky
// File API
// SkyDB

@@ -42,8 +52,9 @@ // SkyDB helpers

* @class
* @param [portalUrl] The portal URL to use to access Skynet, if specified. To use the default portal while passing custom options, use ""
* @param [initialPortalUrl] The initial portal URL to use to access Skynet, if specified. A request will be made to this URL to get the actual portal URL. To use the default portal while passing custom options, pass "".
* @param [customOptions] Configuration for the client.
*/
constructor(portalUrl = (0, _url.defaultPortalUrl)(), customOptions = {}) {
(0, _defineProperty2.default)(this, "portalUrl", void 0);
constructor(initialPortalUrl = "", customOptions = {}) {
(0, _defineProperty2.default)(this, "customOptions", void 0);
(0, _defineProperty2.default)(this, "initialPortalUrl", void 0);
(0, _defineProperty2.default)(this, "givenPortalUrl", void 0);
(0, _defineProperty2.default)(this, "uploadFile", _upload.uploadFile);

@@ -65,2 +76,8 @@ (0, _defineProperty2.default)(this, "uploadFileRequest", _upload.uploadFileRequest);

(0, _defineProperty2.default)(this, "resolveHns", _download.resolveHns);
(0, _defineProperty2.default)(this, "extractDomain", _utils.extractDomain);
(0, _defineProperty2.default)(this, "getFullDomainUrl", _utils.getFullDomainUrl);
(0, _defineProperty2.default)(this, "loadMySky", _mysky.loadMySky);
(0, _defineProperty2.default)(this, "file", {
getJSON: _file.getJSON.bind(this)
});
(0, _defineProperty2.default)(this, "db", {

@@ -73,8 +90,72 @@ getJSON: _skydb.getJSON.bind(this),

getEntryUrl: _registry.getEntryUrl.bind(this),
setEntry: _registry.setEntry.bind(this)
setEntry: _registry.setEntry.bind(this),
postSignedEntry: _registry.postSignedEntry.bind(this)
});
this.portalUrl = portalUrl;
if (initialPortalUrl === "") {
// Portal was not given, use the default portal URL. We'll still make a request for the resolved portal URL.
initialPortalUrl = (0, _url.defaultPortalUrl)();
} else {
// Portal was given, don't make the request for the resolved portal URL.
this.givenPortalUrl = initialPortalUrl;
}
this.initialPortalUrl = initialPortalUrl;
this.customOptions = customOptions;
}
/**
* Make the request for the API portal URL.
*
* @returns - A promise that resolves when the request is complete.
*/
/* istanbul ignore next */
async initPortalUrl() {
if (!SkynetClient.resolvedPortalUrl) {
SkynetClient.resolvedPortalUrl = new Promise((resolve, reject) => {
this.executeRequest({ ...this.customOptions,
method: "head",
url: this.initialPortalUrl,
endpointPath: "/"
}).then(response => {
/* istanbul ignore next */
if (typeof response.headers === "undefined") {
reject(new Error("Did not get 'headers' in response despite a successful request. Please try again and report this issue to the devs if it persists."));
}
const portalUrl = response.headers["skynet-portal-api"];
if (!portalUrl) {
reject(new Error("Could not get portal URL for the given portal"));
}
resolve(portalUrl);
});
});
}
await SkynetClient.resolvedPortalUrl;
return;
}
/**
* Returns the API portal URL. Makes the request to get it if not done so already.
*
* @returns - the portal URL.
*/
/* istanbul ignore next */
async portalUrl() {
if (this.givenPortalUrl) {
return this.givenPortalUrl;
} // Make the request if needed and not done so.
this.initPortalUrl();
return await SkynetClient.resolvedPortalUrl; // eslint-disable-line
}
/**
* Creates and executes a request.

@@ -88,3 +169,3 @@ *

executeRequest(config) {
async executeRequest(config) {
if (config.skykeyName || config.skykeyId) {

@@ -100,3 +181,4 @@ throw new Error("Unimplemented: skykeys have not been implemented in this SDK");

url = (0, _url.makeUrl)(this.portalUrl, config.endpointPath, (_config$extraPath = config.extraPath) !== null && _config$extraPath !== void 0 ? _config$extraPath : "");
const portalUrl = await this.portalUrl();
url = (0, _url.makeUrl)(portalUrl, config.endpointPath, (_config$extraPath = config.extraPath) !== null && _config$extraPath !== void 0 ? _config$extraPath : "");
}

@@ -138,3 +220,5 @@

maxContentLength: Infinity,
maxBodyLength: Infinity
maxBodyLength: Infinity,
// Allow cross-site cookies.
withCredentials: true
});

@@ -145,2 +229,3 @@ }

exports.SkynetClient = SkynetClient;
exports.SkynetClient = SkynetClient;
(0, _defineProperty2.default)(SkynetClient, "resolvedPortalUrl", void 0);

7

dist/crypto.js

@@ -88,5 +88,6 @@ "use strict";

function encodeString(str) {
const encoded = new Uint8Array(8 + str.length);
encoded.set(encodeNumber(str.length));
encoded.set((0, _string.stringToUint8Array)(str), 8);
const byteArray = (0, _string.stringToUint8Array)(str);
const encoded = new Uint8Array(8 + byteArray.length);
encoded.set(encodeNumber(byteArray.length));
encoded.set(byteArray, 8);
return encoded;

@@ -93,0 +94,0 @@ }

@@ -5,2 +5,4 @@ "use strict";

var _tweak = require("./mysky/tweak");
var _number = require("./utils/number");

@@ -99,2 +101,10 @@

});
it("Should work for mySky.setJSON paths", () => {
const path = "localhost/cert";
const expected = "852b9478b480488fe2d18286d14c92e997f00e22f5d146627246e633897c314f";
const dataKey = (0, _tweak.deriveDiscoverableTweak)(path);
const input = (0, _string.uint8ArrayToString)(dataKey);
const hash = (0, _string.toHexString)((0, _crypto.hashDataKey)(input));
expect(hash).toEqual(expected);
});
});

@@ -113,2 +123,13 @@ describe("hashRegistryValue", () => {

});
it("should match siad for equal input when datakey and data include unicode", () => {
// Hard-code expected values to catch any breaking changes.
// "h" is the hash generated by siad with the same input parameters
const h = "ff3b430675a0666e7461bc34aec9f66e21183d061f0b8232dd28ca90cc6ea5ca";
const hash = (0, _crypto.hashRegistryEntry)({
datakey: "HelloWorld π",
data: "abc π",
revision: BigInt(123456789)
});
expect((0, _string.toHexString)(hash)).toEqual(h);
});
});

@@ -18,2 +18,3 @@ "use strict";

exports.resolveHns = resolveHns;
exports.defaultDownloadOptions = void 0;

@@ -28,2 +29,3 @@ var _skylink = require("./utils/skylink");

};
exports.defaultDownloadOptions = defaultDownloadOptions;
const defaultDownloadHnsOptions = { ...(0, _skylink.defaultOptions)("/hns"),

@@ -45,3 +47,3 @@ hnsSubdomain: "hns"

function downloadFile(skylinkUrl, customOptions) {
async function downloadFile(skylinkUrl, customOptions) {
/* istanbul ignore next */

@@ -57,3 +59,3 @@ if (typeof skylinkUrl !== "string") {

};
const url = this.getSkylinkUrl(skylinkUrl, opts); // Download the url.
const url = await this.getSkylinkUrl(skylinkUrl, opts); // Download the url.

@@ -86,3 +88,3 @@ window.location.assign(url);

};
const url = this.getHnsUrl(domain, opts); // Download the url.
const url = await this.getHnsUrl(domain, opts); // Download the url.

@@ -104,10 +106,3 @@ window.location.assign(url);

function getSkylinkUrl(skylinkUrl, customOptions) {
var _opts$query;
/* istanbul ignore next */
if (typeof skylinkUrl !== "string") {
throw new Error("Expected parameter skylinkUrl to be type string, was type ".concat(typeof skylinkUrl));
}
async function getSkylinkUrl(skylinkUrl, customOptions) {
const opts = { ...defaultDownloadOptions,

@@ -117,65 +112,4 @@ ...this.customOptions,

};
const query = (_opts$query = opts.query) !== null && _opts$query !== void 0 ? _opts$query : {};
if (opts.download) {
// Set the "attachment" parameter.
query.attachment = true;
}
if (opts.noResponseMetadata) {
// Set the "no-response-metadata" parameter.
query["no-response-metadata"] = true;
} // URL-encode the path.
let path = "";
if (opts.path) {
if (typeof opts.path !== "string") {
throw new Error("opts.path has to be a string, ".concat(typeof opts.path, " provided"));
} // Encode each element of the path separately and join them.
//
// Don't use encodeURI because it does not encode characters such as '?'
// etc. These are allowed as filenames on Skynet and should be encoded so
// they are not treated as URL separators.
path = opts.path.split("/").map(element => encodeURIComponent(element)).join("/");
}
let url;
if (opts.subdomain) {
var _parseSkylink;
// Get the path from the skylink. Use the empty string if not found.
const skylinkPath = (_parseSkylink = (0, _skylink.parseSkylink)(skylinkUrl, {
onlyPath: true
})) !== null && _parseSkylink !== void 0 ? _parseSkylink : ""; // Get just the skylink.
let skylink = (0, _skylink.parseSkylink)(skylinkUrl);
if (skylink === null) {
throw new Error("Could not get skylink out of input '".concat(skylinkUrl, "'"));
} // Convert the skylink (without the path) to base32.
skylink = (0, _skylink.convertSkylinkToBase32)(skylink);
url = (0, _url.addSubdomain)(this.portalUrl, skylink);
url = (0, _url.makeUrl)(url, skylinkPath, path);
} else {
// Get the skylink including the path.
const skylink = (0, _skylink.parseSkylink)(skylinkUrl, {
includePath: true
});
if (skylink === null) {
throw new Error("Could not get skylink with path out of input '".concat(skylinkUrl, "'"));
} // Add additional path if passed in.
url = (0, _url.makeUrl)(this.portalUrl, opts.endpointPath, skylink, path);
}
return (0, _url.addUrlQuery)(url, query);
const portalUrl = await this.portalUrl();
return (0, _url.getSkylinkUrlForPortal)(portalUrl, skylinkUrl, opts);
}

@@ -194,4 +128,4 @@ /**

function getHnsUrl(domain, customOptions) {
var _opts$query2;
async function getHnsUrl(domain, customOptions) {
var _opts$query;

@@ -207,3 +141,3 @@ /* istanbul ignore next */

};
const query = (_opts$query2 = opts.query) !== null && _opts$query2 !== void 0 ? _opts$query2 : {};
const query = (_opts$query = opts.query) !== null && _opts$query !== void 0 ? _opts$query : {};

@@ -219,3 +153,4 @@ if (opts.download) {

domain = (0, _string.trimUriPrefix)(domain, _skylink.uriHandshakePrefix);
const url = opts.subdomain ? (0, _url.addSubdomain)((0, _url.addSubdomain)(this.portalUrl, opts.hnsSubdomain), domain) : (0, _url.makeUrl)(this.portalUrl, opts.endpointPath, domain);
const portalUrl = await this.portalUrl();
const url = opts.subdomain ? (0, _url.addSubdomain)((0, _url.addSubdomain)(portalUrl, opts.hnsSubdomain), domain) : (0, _url.makeUrl)(portalUrl, opts.endpointPath, domain);
return (0, _url.addUrlQuery)(url, query);

@@ -235,3 +170,3 @@ }

function getHnsresUrl(domain, customOptions) {
async function getHnsresUrl(domain, customOptions) {
/* istanbul ignore next */

@@ -247,3 +182,4 @@ if (typeof domain !== "string") {

domain = (0, _string.trimUriPrefix)(domain, _skylink.uriHandshakeResolverPrefix);
return (0, _url.makeUrl)(this.portalUrl, opts.endpointPath, domain);
const portalUrl = await this.portalUrl();
return (0, _url.makeUrl)(portalUrl, opts.endpointPath, domain);
}

@@ -274,3 +210,3 @@ /**

};
const url = this.getSkylinkUrl(skylinkUrl, opts);
const url = await this.getSkylinkUrl(skylinkUrl, opts);
const response = await this.executeRequest({ ...opts,

@@ -318,3 +254,3 @@ method: "head",

};
const url = this.getSkylinkUrl(skylinkUrl, opts);
const url = await this.getSkylinkUrl(skylinkUrl, opts);
return this.getFileContentRequest(url, opts);

@@ -339,3 +275,3 @@ }

};
const url = this.getHnsUrl(domain, opts);
const url = await this.getHnsUrl(domain, opts);
return this.getFileContentRequest(url, opts);

@@ -399,3 +335,3 @@ }

function openFile(skylinkUrl, customOptions) {
async function openFile(skylinkUrl, customOptions) {
/* istanbul ignore next */

@@ -410,3 +346,3 @@ if (typeof skylinkUrl !== "string") {

};
const url = this.getSkylinkUrl(skylinkUrl, opts);
const url = await this.getSkylinkUrl(skylinkUrl, opts);
window.open(url, "_blank");

@@ -437,3 +373,3 @@ return url;

};
const url = this.getHnsUrl(domain, opts); // Open the url in a new tab.
const url = await this.getHnsUrl(domain, opts); // Open the url in a new tab.

@@ -465,3 +401,3 @@ window.open(url, "_blank");

};
const url = this.getHnsresUrl(domain, opts); // Get the txt record from the hnsres domain on the portal.
const url = await this.getHnsresUrl(domain, opts); // Get the txt record from the hnsres domain on the portal.

@@ -468,0 +404,0 @@ const response = await this.executeRequest({ ...opts,

@@ -13,2 +13,4 @@ "use strict";

var _url = require("./utils/url");
const portalUrl = _index.defaultSkynetPortalUrl;

@@ -19,3 +21,3 @@ const hnsLink = "foo";

const skylinkBase32 = "bg06v2tidkir84hg0s1s4t97jaeoaa1jse1svrad657u070c9calq4g";
const skylinkUrl = client.getSkylinkUrl(skylink);
const skylinkUrl = (0, _url.getSkylinkUrlForPortal)(portalUrl, skylink);
const sialink = "".concat(_index.uriSkynetPrefix).concat(skylink);

@@ -38,5 +40,5 @@ const validSkylinkVariations = (0, _testing.combineStrings)(["", "sia:", "sia://", "https://siasky.net/", "https://foo.siasky.net/", "https://".concat(skylinkBase32, ".siasky.net/")], [skylink], ["", "/", "//", "/foo", "/foo/", "/foo/bar", "/foo/bar/", "/foo/bar//"], ["", "?", "?foo=bar", "?foo=bar&bar=baz"], ["", "#", "#foo", "#foo?bar"]);

describe("downloadFile", () => {
it.each(validSkylinkVariations)("should download with attachment set from skylink %s", fullSkylink => {
it.each(validSkylinkVariations)("should download with attachment set from skylink %s", async fullSkylink => {
mockLocationAssign.mockClear();
const url = client.downloadFile(fullSkylink);
const url = await client.downloadFile(fullSkylink);
const path = (0, _testing.extractNonSkylinkPath)(fullSkylink, skylink);

@@ -52,4 +54,4 @@ let fullExpectedUrl = "".concat(expectedUrl).concat(path).concat(attachment); // Change ?attachment=true to &attachment=true if need be.

});
it("should download with the optional path being correctly URI-encoded", () => {
const url = client.downloadFile(skylink, {
it("should download with the optional path being correctly URI-encoded", async () => {
const url = await client.downloadFile(skylink, {
path: "dir/test?encoding"

@@ -59,4 +61,4 @@ });

});
it("should download with query parameters being appended to the URL", () => {
const url = client.downloadFile(skylink, {
it("should download with query parameters being appended to the URL", async () => {
const url = await client.downloadFile(skylink, {
query: {

@@ -76,10 +78,10 @@ name: "test"

describe("getHnsUrl", () => {
it.each(validHnsLinkVariations)("should return correctly formed hns URL using hns link %s", input => {
expect(client.getHnsUrl(input)).toEqual(expectedHnsUrl);
expect(client.getHnsUrl(input, {
it.each(validHnsLinkVariations)("should return correctly formed hns URL using hns link %s", async input => {
expect(await client.getHnsUrl(input)).toEqual(expectedHnsUrl);
expect(await client.getHnsUrl(input, {
subdomain: true
})).toEqual(expectedHnsUrlSubdomain);
});
it("should return correctly formed hns URL with forced download", () => {
const url = client.getHnsUrl(hnsLink, {
it("should return correctly formed hns URL with forced download", async () => {
const url = await client.getHnsUrl(hnsLink, {
download: true

@@ -89,4 +91,4 @@ });

});
it("should return correctly formed hns URL with no-response-metadata set", () => {
const url = client.getHnsUrl(hnsLink, {
it("should return correctly formed hns URL with no-response-metadata set", async () => {
const url = await client.getHnsUrl(hnsLink, {
noResponseMetadata: true

@@ -98,4 +100,4 @@ });

describe("getHnsresUrl", () => {
it.each(validHnsresLinkVariations)("should return correctly formed hnsres URL using hnsres link %s", input => {
expect(client.getHnsresUrl(input)).toEqual(expectedHnsresUrl);
it.each(validHnsresLinkVariations)("should return correctly formed hnsres URL using hnsres link %s", async input => {
expect(await client.getHnsresUrl(input)).toEqual(expectedHnsresUrl);
});

@@ -105,16 +107,16 @@ });

const expectedUrl = "".concat(portalUrl, "/").concat(skylink);
it.each(validSkylinkVariations)("should return correctly formed skylink URL using skylink %s", fullSkylink => {
it.each(validSkylinkVariations)("should return correctly formed skylink URL using skylink %s", async fullSkylink => {
const path = (0, _testing.extractNonSkylinkPath)(fullSkylink, skylink);
expect(client.getSkylinkUrl(fullSkylink)).toEqual("".concat(expectedUrl).concat(path));
expect(await client.getSkylinkUrl(fullSkylink)).toEqual("".concat(expectedUrl).concat(path));
});
it("should return correctly formed URLs when path is given", () => {
expect(client.getSkylinkUrl(skylink, {
it("should return correctly formed URLs when path is given", async () => {
expect(await client.getSkylinkUrl(skylink, {
path: "foo/bar"
})).toEqual("".concat(expectedUrl, "/foo/bar"));
expect(client.getSkylinkUrl(skylink, {
expect(await client.getSkylinkUrl(skylink, {
path: "foo?bar"
})).toEqual("".concat(expectedUrl, "/foo%3Fbar"));
});
it("should return correctly formed URL with forced download", () => {
const url = client.getSkylinkUrl(skylink, {
it("should return correctly formed URL with forced download", async () => {
const url = await client.getSkylinkUrl(skylink, {
download: true,

@@ -125,4 +127,4 @@ endpointPath: "skynet/skylink"

});
it("should return correctly formed URLs with forced download and path", () => {
const url = client.getSkylinkUrl(skylink, {
it("should return correctly formed URLs with forced download and path", async () => {
const url = await client.getSkylinkUrl(skylink, {
download: true,

@@ -133,4 +135,4 @@ path: "foo?bar"

});
it("should return correctly formed URLs with no-response-metadata set", () => {
const url = client.getSkylinkUrl(skylink, {
it("should return correctly formed URLs with no-response-metadata set", async () => {
const url = await client.getSkylinkUrl(skylink, {
noResponseMetadata: true

@@ -140,4 +142,4 @@ });

});
it("should return correctly formed URLs with no-response-metadata set and with forced download", () => {
const url = client.getSkylinkUrl(skylink, {
it("should return correctly formed URLs with no-response-metadata set and with forced download", async () => {
const url = await client.getSkylinkUrl(skylink, {
download: true,

@@ -149,5 +151,5 @@ noResponseMetadata: true

const expectedBase32 = "https://".concat(skylinkBase32, ".siasky.net");
it.each(validSkylinkVariations)("should convert base64 skylink to base32 using skylink %s", fullSkylink => {
it.each(validSkylinkVariations)("should convert base64 skylink to base32 using skylink %s", async fullSkylink => {
const path = (0, _testing.extractNonSkylinkPath)(fullSkylink, skylink);
const url = client.getSkylinkUrl(fullSkylink, {
const url = await client.getSkylinkUrl(fullSkylink, {
subdomain: true

@@ -157,14 +159,14 @@ });

});
it("should throw if passing a non-string path", () => {
it("should throw if passing a non-string path", async () => {
// @ts-expect-error we only check this use case in case someone ignores typescript typing
expect(() => client.getSkylinkUrl(skylink, {
await expect(client.getSkylinkUrl(skylink, {
path: true
})).toThrow();
})).rejects.toThrowError("opts.path has to be a string, boolean provided");
});
const invalidCases = ["123", "".concat(skylink, "xxx"), "".concat(skylink, "xxx/foo"), "".concat(skylink, "xxx?foo")];
it.each(invalidCases)("should throw on invalid skylink %s", invalidSkylink => {
expect(() => client.getSkylinkUrl(invalidSkylink)).toThrow();
expect(() => client.getSkylinkUrl(invalidSkylink, {
it.each(invalidCases)("should throw on invalid skylink %s", async invalidSkylink => {
await expect(client.getSkylinkUrl(invalidSkylink)).rejects.toThrow();
await expect(client.getSkylinkUrl(invalidSkylink, {
subdomain: true
})).toThrow();
})).rejects.toThrow();
});

@@ -176,2 +178,5 @@ });

mock = new _axiosMockAdapter.default(_axios.default);
mock.onHead(portalUrl).replyOnce(200, {}, {
"skynet-portal-api": portalUrl
});
});

@@ -186,4 +191,4 @@ const skynetFileMetadata = {

it.each(validSkylinkVariations)("should successfully fetch skynet file headers from skylink %s", async fullSkylink => {
const skylinkUrl = client.getSkylinkUrl(fullSkylink);
mock.onHead(skylinkUrl).reply(200, {}, headersFull);
const skylinkUrl = await client.getSkylinkUrl(fullSkylink);
mock.onHead(skylinkUrl).replyOnce(200, {}, headersFull);
const {

@@ -195,4 +200,4 @@ metadata

it.each(validSkylinkVariations)("should quietly return nothing when skynet metadata headers not present for skylink %s", async fullSkylink => {
const skylinkUrl = client.getSkylinkUrl(fullSkylink);
mock.onHead(skylinkUrl).reply(200, {}, {});
const skylinkUrl = await client.getSkylinkUrl(fullSkylink);
mock.onHead(skylinkUrl).replyOnce(200, {}, {});
const {

@@ -204,3 +209,3 @@ metadata

it("should throw if no headers were returned", async () => {
mock.onHead(skylinkUrl).reply(200, {});
mock.onHead(skylinkUrl).replyOnce(200, {});
await expect(client.getMetadata(skylink)).rejects.toThrowError("Did not get 'headers' in response despite a successful request. Please try again and report this issue to the devs if it persists.");

@@ -213,2 +218,5 @@ });

mock = new _axiosMockAdapter.default(_axios.default);
mock.onHead(portalUrl).replyOnce(200, {}, {
"skynet-portal-api": portalUrl
});
});

@@ -227,4 +235,4 @@ const skynetFileMetadata = {

it.each(validSkylinkVariations)("should successfully fetch skynet file content for %s", async input => {
const skylinkUrl = client.getSkylinkUrl(input);
mock.onGet(skylinkUrl).reply(200, skynetFileContents, fullHeaders);
const skylinkUrl = await client.getSkylinkUrl(input);
mock.onGet(skylinkUrl).replyOnce(200, skynetFileContents, fullHeaders);
const {

@@ -243,4 +251,4 @@ data,

it.each(validSkylinkVariations)("should successfully fetch skynet file content even when headers are missing for %s", async input => {
const skylinkUrl = client.getSkylinkUrl(input);
mock.onGet(skylinkUrl).reply(200, skynetFileContents, headers);
const skylinkUrl = await client.getSkylinkUrl(input);
mock.onGet(skylinkUrl).replyOnce(200, skynetFileContents, headers);
const {

@@ -258,7 +266,7 @@ data,

it("should throw if data is not returned", async () => {
mock.onGet(expectedUrl).reply(200);
mock.onGet(expectedUrl).replyOnce(200);
await expect(client.getFileContent(skylink)).rejects.toThrowError("Did not get 'data' in response despite a successful request. Please try again and report this issue to the devs if it persists.");
});
it("should throw if headers are not returned", async () => {
mock.onGet(expectedUrl).reply(200, {});
mock.onGet(expectedUrl).replyOnce(200, {});
await expect(client.getFileContent(skylink)).rejects.toThrowError("Did not get 'headers' in response despite a successful request. Please try again and report this issue to the devs if it persists.");

@@ -271,2 +279,5 @@ });

mock = new _axiosMockAdapter.default(_axios.default);
mock.onHead(portalUrl).replyOnce(200, {}, {
"skynet-portal-api": portalUrl
});
});

@@ -280,3 +291,3 @@ const skynetFileContents = {

it.each(validHnsLinkVariations)("should successfully fetch skynet file content", async input => {
const hnsUrl = client.getHnsUrl(input);
const hnsUrl = await client.getHnsUrl(input);
mock.onGet(hnsUrl).reply(200, skynetFileContents, headers);

@@ -291,6 +302,6 @@ const {

const windowOpen = jest.spyOn(window, "open").mockImplementation();
it.each(validSkylinkVariations)("should call window.openFile when calling openFile with skylink %s", fullSkylink => {
it.each(validSkylinkVariations)("should call window.openFile when calling openFile with skylink %s", async fullSkylink => {
windowOpen.mockReset();
const path = (0, _testing.extractNonSkylinkPath)(fullSkylink, skylink);
client.openFile(fullSkylink);
await client.openFile(fullSkylink);
expect(windowOpen).toHaveBeenCalledTimes(1);

@@ -312,2 +323,5 @@ expect(windowOpen).toHaveBeenCalledWith("".concat(expectedUrl).concat(path), "_blank");

mock = new _axiosMockAdapter.default(_axios.default);
mock.onHead(portalUrl).replyOnce(200, {}, {
"skynet-portal-api": portalUrl
});
});

@@ -331,14 +345,15 @@ it("should set domain with the portal and hns link and then call window.openFile", async () => {

mock = new _axiosMockAdapter.default(_axios.default);
mock.onGet(expectedHnsresUrl).reply(200, {
mock.onHead(portalUrl).replyOnce(200, {}, {
"skynet-portal-api": portalUrl
});
mock.onGet(expectedHnsresUrl).replyOnce(200, {
skylink
});
});
it("should call axios.get with the portal and hnsres link and return the json body", async () => {
for (const input of validHnsresLinkVariations) {
mock.resetHistory();
const data = await client.resolveHns(input);
expect(mock.history.get.length).toBe(1);
expect(data.skylink).toEqual(skylink);
}
it.each(validHnsresLinkVariations)("should call axios.get with the portal and hnsres link for %s and return the json body", async hnsresLink => {
mock.resetHistory();
const data = await client.resolveHns(hnsresLink);
expect(mock.history.get.length).toBe(1);
expect(data.skylink).toEqual(skylink);
});
});

@@ -30,2 +30,8 @@ "use strict";

});
Object.defineProperty(exports, "signEntry", {
enumerable: true,
get: function () {
return _registry.signEntry;
}
});
Object.defineProperty(exports, "getRelativeFilePath", {

@@ -85,2 +91,44 @@ enumerable: true,

});
Object.defineProperty(exports, "extractDomainForPortal", {
enumerable: true,
get: function () {
return _url.extractDomainForPortal;
}
});
Object.defineProperty(exports, "getFullDomainUrlForPortal", {
enumerable: true,
get: function () {
return _url.getFullDomainUrlForPortal;
}
});
Object.defineProperty(exports, "getEntryUrlForPortal", {
enumerable: true,
get: function () {
return _url.getEntryUrlForPortal;
}
});
Object.defineProperty(exports, "getSkylinkUrlForPortal", {
enumerable: true,
get: function () {
return _url.getSkylinkUrlForPortal;
}
});
Object.defineProperty(exports, "DacLibrary", {
enumerable: true,
get: function () {
return _mysky.DacLibrary;
}
});
Object.defineProperty(exports, "mySkyDomain", {
enumerable: true,
get: function () {
return _mysky.mySkyDomain;
}
});
Object.defineProperty(exports, "mySkyDevDomain", {
enumerable: true,
get: function () {
return _mysky.mySkyDevDomain;
}
});

@@ -91,2 +139,4 @@ var _client = require("./client");

var _registry = require("./registry");
var _file = require("./utils/file");

@@ -98,2 +148,4 @@

var _url = require("./utils/url");
var _url = require("./utils/url");
var _mysky = require("./mysky");

@@ -22,4 +22,11 @@ "use strict";

expect(client).toHaveProperty("uploadFile");
expect(client).toHaveProperty("uploadDirectory"); // SkyDB
expect(client).toHaveProperty("uploadDirectory"); // MySky
expect(client).toHaveProperty("extractDomain");
expect(client).toHaveProperty("getFullDomainUrl");
expect(client).toHaveProperty("loadMySky"); // File
expect(client).toHaveProperty("file");
expect(client.file).toHaveProperty("getJSON"); // SkyDB
expect(client).toHaveProperty("db");

@@ -26,0 +33,0 @@ expect(client.db).toHaveProperty("getJSON");

@@ -7,3 +7,3 @@ "use strict";

var _number = require("./utils/number");
var _string = require("./utils/string");

@@ -14,2 +14,22 @@ // To test a specific server, e.g. SKYNET_JS_INTEGRATION_TEST_SERVER=https://eu-fin-1.siasky.net yarn test src/integration.test.ts

const dataKey = "HelloWorld";
expect.extend({
toEqualPortalUrl(received, argument) {
const prefix = "".concat(received.split("//", 1)[0], "//");
const expectedUrl = (0, _string.trimPrefix)(argument, prefix);
const receivedUrl = (0, _string.trimPrefix)(received, prefix); // Support the case where we receive siasky.net while expecting eu-fin-1.siasky.net.
if (!expectedUrl.endsWith(receivedUrl)) {
return {
pass: false,
message: () => "expected ".concat(received, " to equal ").concat(argument)
};
}
return {
pass: true,
message: () => "expected ".concat(received, " not to equal ").concat(argument)
};
}
});
describe("Integration test for portal ".concat(portal), () => {

@@ -24,6 +44,6 @@ describe("SkyDB end to end integration tests", () => {

data,
revision
skylink
} = await client.db.getJSON(publicKey, "foo");
expect(data).toBeNull();
expect(revision).toBeNull();
expect(skylink).toBeNull();
});

@@ -37,16 +57,28 @@ it("Should set and get new entries", async () => {

data: "thisistext"
}; // Set the file in the SkyDB.
};
const json2 = {
data: "foo2"
}; // Set the file in SkyDB.
await client.db.setJSON(privateKey, dataKey, json); // get the file in the SkyDB
await client.db.setJSON(privateKey, dataKey, json); // Get the file in SkyDB.
const {
data,
revision
skylink
} = await client.db.getJSON(publicKey, dataKey);
expect(data).toEqual(json); // Revision should be 0.
expect(data).toEqual(json);
expect(skylink).toBeTruthy(); // Set the file again.
expect(revision).toEqual(BigInt(0));
});
it("Should set and get entries with the revision at the max allowed", async () => {
await client.db.setJSON(privateKey, dataKey, json2);
const {
data: data2,
skylink: skylink2
} = await client.db.getJSON(publicKey, dataKey);
expect(data2).toEqual(json2);
expect(skylink2).toBeTruthy();
}); // Regression test: Use some strange data keys that have failed in previous versions.
const dataKeys = [".", "..", "http://localhost:8000/", ""];
it.each(dataKeys)("Should set and get new entry with dataKey '%s'", async dataKey => {
const {
publicKey,

@@ -56,18 +88,11 @@ privateKey

const json = {
data: "testnumber2"
data: "thisistext"
};
await client.db.setJSON(privateKey, dataKey, json, _number.MAX_REVISION);
const actual = await client.db.getJSON(publicKey, dataKey);
expect(actual.data).toEqual(json);
expect(actual.revision).toEqual(_number.MAX_REVISION);
});
it("Try setting the revision higher than the uint64 max", async () => {
await client.db.setJSON(privateKey, dataKey, json);
const {
privateKey
} = (0, _index.genKeyPairAndSeed)();
const json = {
data: "testnumber3"
};
const largeint = _number.MAX_REVISION + BigInt(1);
await expect(client.db.setJSON(privateKey, dataKey, json, largeint)).rejects.toThrowError("Argument 18446744073709551616 does not fit in a 64-bit unsigned integer; exceeds 2^64-1");
data,
skylink
} = await client.db.getJSON(publicKey, dataKey);
expect(data).toEqual(json);
expect(skylink).toBeTruthy();
});

@@ -189,3 +214,3 @@ });

expect(metadata).toEqual(plaintextMetadata);
expect(portalUrl).toEqual(portal);
expect(portalUrl).toEqualPortalUrl(portal);
expect(skylink).toEqual(returnedSkylink);

@@ -211,3 +236,3 @@ });

expect(metadata).toEqual(plaintextMetadata);
expect(portalUrl).toEqual(portal);
expect(portalUrl).toEqualPortalUrl(portal);
expect(skylink).toEqual(returnedSkylink);

@@ -214,0 +239,0 @@ });

@@ -9,3 +9,5 @@ "use strict";

exports.setEntry = setEntry;
exports.regexRevisionNoQuotes = exports.MAX_GET_ENTRY_TIMEOUT = void 0;
exports.signEntry = signEntry;
exports.postSignedEntry = postSignedEntry;
exports.regexRevisionNoQuotes = exports.MAX_GET_ENTRY_TIMEOUT = exports.defaultGetEntryOptions = void 0;

@@ -29,2 +31,3 @@ var _buffer = require("buffer");

};
exports.defaultGetEntryOptions = defaultGetEntryOptions;
const defaultSetEntryOptions = { ...(0, _skylink.defaultOptions)("/skynet/registry")

@@ -70,3 +73,3 @@ };

};
const url = this.registry.getEntryUrl(publicKey, dataKey, opts);
const url = await this.registry.getEntryUrl(publicKey, dataKey, opts);
let response;

@@ -140,3 +143,3 @@

if (response.data.data) {
data = _buffer.Buffer.from((0, _string.hexToUint8Array)(response.data.data)).toString();
data = (0, _string.uint8ArrayToString)((0, _string.hexToUint8Array)(response.data.data));
}

@@ -172,14 +175,3 @@

function getEntryUrl(publicKey, dataKey, customOptions) {
/* istanbul ignore next */
if (typeof publicKey !== "string") {
throw new Error("Expected parameter publicKey to be type string, was type ".concat(typeof publicKey));
}
/* istanbul ignore next */
if (typeof dataKey !== "string") {
throw new Error("Expected parameter dataKey to be type string, was type ".concat(typeof dataKey));
}
async function getEntryUrl(publicKey, dataKey, customOptions) {
const opts = { ...defaultGetEntryOptions,

@@ -189,23 +181,4 @@ ...this.customOptions,

};
const timeout = opts.timeout;
if (!Number.isInteger(timeout) || timeout > MAX_GET_ENTRY_TIMEOUT || timeout < 1) {
throw new Error("Invalid 'timeout' parameter '".concat(timeout, "', needs to be an integer between 1s and ").concat(MAX_GET_ENTRY_TIMEOUT, "s"));
} // Trim the prefix if it was passed in.
publicKey = (0, _string.trimPrefix)(publicKey, "ed25519:");
if (!(0, _string.isHexString)(publicKey)) {
throw new Error("Given public key '".concat(publicKey, "' is not a valid hex-encoded string or contains an invalid prefix"));
}
const query = {
publickey: "ed25519:".concat(publicKey),
datakey: (0, _string.toHexString)((0, _crypto.hashDataKey)(dataKey)),
timeout
};
let url = (0, _url.makeUrl)(this.portalUrl, opts.endpointPath);
url = (0, _url.addUrlQuery)(url, query);
return url;
const portalUrl = await this.portalUrl();
return (0, _url.getEntryUrlForPortal)(portalUrl, publicKey, dataKey, opts);
}

@@ -243,6 +216,5 @@ /**

};
const privateKeyArray = (0, _string.hexToUint8Array)(privateKey); // Sign the entry.
const privateKeyArray = (0, _string.hexToUint8Array)(privateKey);
const signature = await signEntry(privateKey, entry);
const signature = (0, _tweetnacl.sign)((0, _crypto.hashRegistryEntry)(entry), privateKeyArray);
const {

@@ -252,6 +224,22 @@ publicKey: publicKeyArray

return await this.registry.postSignedEntry((0, _string.toHexString)(publicKeyArray), entry, signature, opts);
}
async function signEntry(privateKey, entry) {
const privateKeyArray = (0, _string.hexToUint8Array)(privateKey); // Sign the entry.
// TODO: signature type should be Signature?
return (0, _tweetnacl.sign)((0, _crypto.hashRegistryEntry)(entry), privateKeyArray);
}
async function postSignedEntry(publicKey, entry, signature, customOptions) {
// TODO: Check for valid imputs.
const opts = { ...defaultSetEntryOptions,
...this.customOptions,
...customOptions
};
const data = {
publickey: {
algorithm: "ed25519",
key: Array.from(publicKeyArray)
key: Array.from((0, _string.hexToUint8Array)(publicKey))
},

@@ -258,0 +246,0 @@ datakey: (0, _string.toHexString)((0, _crypto.hashDataKey)(entry.datakey)),

@@ -15,2 +15,4 @@ "use strict";

var _url = require("./utils/url");
const {

@@ -23,3 +25,3 @@ publicKey,

const dataKey = "app";
const registryLookupUrl = client.registry.getEntryUrl(publicKey, dataKey);
const registryLookupUrl = (0, _url.getEntryUrlForPortal)(portalUrl, publicKey, dataKey);
describe("getEntry", () => {

@@ -72,16 +74,16 @@ let mock;

const encodedDK = "7c96a0537ab2aaac9cfe0eca217732f4e10791625b4ab4c17e4d91c8078713b9";
it("should generate the correct registry url for the given entry", () => {
const url = client.registry.getEntryUrl(publicKey, dataKey);
it("should generate the correct registry url for the given entry", async () => {
const url = await client.registry.getEntryUrl(publicKey, dataKey);
expect(url).toEqual("".concat(portalUrl, "/skynet/registry?publickey=").concat(encodedPK, "&datakey=").concat(encodedDK, "&timeout=5"));
});
it("Should throw if the timeout is not an integer", () => {
it("Should throw if the timeout is not an integer", async () => {
const {
publicKey
} = (0, _crypto.genKeyPairAndSeed)();
expect(() => client.registry.getEntryUrl(publicKey, dataKey, {
await expect(client.registry.getEntryUrl(publicKey, dataKey, {
timeout: 1.5
})).toThrowError("Invalid 'timeout' parameter '1.5', needs to be an integer between 1s and 300s");
})).rejects.toThrowError("Invalid 'timeout' parameter '1.5', needs to be an integer between 1s and 300s");
});
it("should trim the prefix if it is provided", () => {
const url = client.registry.getEntryUrl("ed25519:".concat(publicKey), dataKey);
it("should trim the prefix if it is provided", async () => {
const url = await client.registry.getEntryUrl("ed25519:".concat(publicKey), dataKey);
expect(url).toEqual("".concat(portalUrl, "/skynet/registry?publickey=").concat(encodedPK, "&datakey=").concat(encodedDK, "&timeout=5"));

@@ -88,0 +90,0 @@ });

@@ -8,2 +8,4 @@ "use strict";

exports.setJSON = setJSON;
exports.getOrCreateRegistryEntry = getOrCreateRegistryEntry;
exports.JSON_RESPONSE_VERSION = void 0;

@@ -18,2 +20,5 @@ var _tweetnacl = require("tweetnacl");

const JSON_RESPONSE_VERSION = 2;
exports.JSON_RESPONSE_VERSION = JSON_RESPONSE_VERSION;
/**

@@ -41,3 +46,3 @@ * Gets the JSON object corresponding to the publicKey and dataKey.

data: null,
revision: null
skylink: null
};

@@ -56,5 +61,19 @@ } // Download the data in that Skylink.

if (!(data["_data"] && data["_v"])) {
// Legacy data prior to v4, return as-is.
return {
data,
skylink
};
}
const actualData = data["_data"];
if (typeof actualData !== "object" || data === null) {
throw new Error("File data '_data' for the entry at data key '".concat(dataKey, "' is not JSON."));
}
return {
data,
revision: entry.revision
data: actualData,
skylink
};

@@ -69,9 +88,8 @@ }

* @param json - The JSON data to set.
* @param [revision] - The revision number for the data entry.
* @param [customOptions] - Additional settings that can optionally be set.
* @throws - Will throw if the given entry revision does not fit in 64 bits, or if the revision was not given, if the latest revision of the entry is the maximum revision allowed.
* @throws - Will throw if the input keys are not valid strings.
*/
async function setJSON(privateKey, dataKey, json, revision, customOptions) {
async function setJSON(privateKey, dataKey, json, customOptions) {
/* istanbul ignore next */

@@ -98,47 +116,62 @@ if (typeof privateKey !== "string") {

...customOptions
};
const {
publicKey: publicKeyArray
} = _tweetnacl.sign.keyPair.fromSecretKey((0, _string.hexToUint8Array)(privateKey));
const [entry, skylink] = await getOrCreateRegistryEntry(this, publicKeyArray, dataKey, json, opts); // Update the registry.
await this.registry.setEntry(privateKey, entry);
return {
data: json,
skylink
};
}
async function getOrCreateRegistryEntry(client, publicKeyArray, dataKey, json, customOptions) {
const opts = { ...client.customOptions,
...customOptions
}; // Set the hidden _data and _v fields.
const data = {
_data: json,
_v: JSON_RESPONSE_VERSION
}; // Create the data to upload to acquire its skylink.
const file = new File([JSON.stringify(json)], dataKey, {
const dataKeyHex = (0, _string.toHexString)((0, _string.stringToUint8Array)(dataKey));
const file = new File([JSON.stringify(data)], "dk:".concat(dataKeyHex), {
type: "application/json"
}); // Start file upload, do not block.
const skyfilePromise = this.uploadFile(file, opts);
let skyfile;
const skyfilePromise = client.uploadFile(file, opts); // Fetch the current value to find out the revision.
//
// Start getEntry, do not block.
if (revision === undefined) {
// fetch the current value to find out the revision.
const {
publicKey
} = _tweetnacl.sign.keyPair.fromSecretKey((0, _string.hexToUint8Array)(privateKey)); // start getEntry, do not block.
const entryPromise = client.registry.getEntry((0, _string.toHexString)(publicKeyArray), dataKey, opts); // Block until both getEntry and uploadFile are finished.
const [signedEntry, skyfile] = await Promise.all([entryPromise, skyfilePromise]);
let revision;
const entryPromise = this.registry.getEntry((0, _string.toHexString)(publicKey), dataKey, opts);
let entry; // Block until both getEntry and Skyfile upload are finished.
if (signedEntry.entry === null) {
revision = BigInt(0);
} else {
revision = signedEntry.entry.revision + BigInt(1);
} // Throw if the revision is already the maximum value.
[entry, skyfile] = await Promise.all([entryPromise, skyfilePromise]);
if (entry.entry === null) {
revision = BigInt(0);
} else {
revision = entry.entry.revision + BigInt(1);
} // Throw if the revision is already the maximum value.
if (revision > _number.MAX_REVISION) {
throw new Error("Current entry already has maximum allowed revision, could not update the entry");
}
} else {
skyfile = await skyfilePromise;
if (revision > _number.MAX_REVISION) {
throw new Error("Current entry already has maximum allowed revision, could not update the entry");
} // Assert the input is 64 bits.
(0, _number.assertUint64)(revision); // build the registry value
(0, _number.assertUint64)(revision); // Build the registry value.
const skylink = skyfile.skylink;
const entry = {
datakey: dataKey,
data: (0, _string.trimUriPrefix)(skyfile.skylink, _skylink.uriSkynetPrefix),
data: (0, _string.trimUriPrefix)(skylink, _skylink.uriSkynetPrefix),
revision
}; // Update the registry.
await this.registry.setEntry(privateKey, entry);
};
return [entry, skylink];
}

@@ -23,5 +23,10 @@ "use strict";

const skylink = "CABAB_1Dt0FJsxqsu_J4TodNCbCGvtFf1Uys_3EgzOlTcg";
const json = {
const jsonData = {
data: "thisistext"
};
const fullJsonData = {
_data: jsonData,
_v: 2
};
const legacyJsonData = jsonData;
const merkleroot = "QAf9Q7dBSbMarLvyeE6HTQmwhr7RX9VMrP9xIMzpU3I";

@@ -32,5 +37,5 @@ const bitfield = 2048;

const registryUrl = "".concat(portalUrl, "/skynet/registry");
const registryLookupUrl = client.registry.getEntryUrl(publicKey, dataKey);
const registryLookupUrl = (0, _url.getEntryUrlForPortal)(portalUrl, publicKey, dataKey);
const uploadUrl = "".concat(portalUrl, "/skynet/skyfile");
const skylinkUrl = client.getSkylinkUrl(skylink);
const skylinkUrl = (0, _url.getSkylinkUrlForPortal)(portalUrl, skylink);
const data = "43414241425f31447430464a73787173755f4a34546f644e4362434776744666315579735f3345677a4f6c546367";

@@ -47,2 +52,5 @@ const revision = 11;

mock = new _axiosMockAdapter.default(_axios.default);
mock.onHead(portalUrl).replyOnce(200, {}, {
"skynet-portal-api": portalUrl
});
mock.resetHistory();

@@ -52,8 +60,16 @@ });

// mock a successful registry lookup
mock.onGet(registryLookupUrl).reply(200, JSON.stringify(entryData));
mock.onGet(skylinkUrl).reply(200, json, {});
mock.onGet(registryLookupUrl).replyOnce(200, JSON.stringify(entryData));
mock.onGet(skylinkUrl).replyOnce(200, fullJsonData, {});
const jsonReturned = await client.db.getJSON(publicKey, dataKey);
expect(jsonReturned.data).toEqual(json);
expect(jsonReturned.data).toEqual(jsonData);
expect(mock.history.get.length).toBe(2);
});
it("should perform a lookup and skylink GET on legacy pre-v4 data", async () => {
// mock a successful registry lookup
mock.onGet(registryLookupUrl).replyOnce(200, JSON.stringify(entryData));
mock.onGet(skylinkUrl).replyOnce(200, legacyJsonData, {});
const jsonReturned = await client.db.getJSON(publicKey, dataKey);
expect(jsonReturned.data).toEqual(jsonData);
expect(mock.history.get.length).toBe(2);
});
it("should return null if no entry is found", async () => {

@@ -63,6 +79,6 @@ mock.onGet(registryLookupUrl).reply(404);

data,
revision
skylink
} = await client.db.getJSON(publicKey, dataKey);
expect(data).toBeNull();
expect(revision).toBeNull();
expect(skylink).toBeNull();
});

@@ -80,2 +96,5 @@ it("should throw if the returned file data is not JSON", async () => {

mock = new _axiosMockAdapter.default(_axios.default);
mock.onHead(portalUrl).replyOnce(200, {}, {
"skynet-portal-api": portalUrl
});
mock.resetHistory(); // mock a successful upload

@@ -91,7 +110,12 @@

// mock a successful registry lookup
mock.onGet(registryLookupUrl).reply(200, JSON.stringify(entryData)); // mock a successful registry update
mock.onGet(registryLookupUrl).replyOnce(200, JSON.stringify(entryData)); // mock a successful registry update
mock.onPost(registryUrl).reply(204); // set data
mock.onPost(registryUrl).replyOnce(204); // set data
await client.db.setJSON(privateKey, dataKey, json); // assert our request history contains the expected amount of requests
const {
data: returnedData,
skylink: returnedSkylink
} = await client.db.setJSON(privateKey, dataKey, jsonData);
expect(returnedData).toEqual(jsonData);
expect(returnedSkylink).toEqual("sia:".concat(skylink)); // assert our request history contains the expected amount of requests

@@ -104,14 +128,2 @@ expect(mock.history.get.length).toBe(1);

});
it("should use the revision if it is passed in", async () => {
// mock a successful registry update
mock.onPost(registryUrl).reply(204); // set data
const updated = await client.db.setJSON(privateKey, dataKey, json, BigInt(revision + 1));
expect(updated); // assert our request history contains the expected amount of requests
expect(mock.history.post.length).toBe(2);
const data = JSON.parse(mock.history.post[1].data);
expect(data).toBeDefined();
expect(data.revision).toEqual(revision + 1);
});
it("should use a revision number of 0 if the lookup failed", async () => {

@@ -122,3 +134,3 @@ mock.onGet(registryLookupUrl).reply(404); // mock a successful registry update

await client.db.setJSON(privateKey, dataKey, json); // assert our request history contains the expected amount of requests
await client.db.setJSON(privateKey, dataKey, jsonData); // assert our request history contains the expected amount of requests

@@ -125,0 +137,0 @@ expect(mock.history.get.length).toBe(1);

"use strict";
var _tweak = require("../mysky/tweak");
var _skylink = require("./skylink");

@@ -46,2 +48,12 @@

});
describe("stringToUint8Array", () => {
it("Should work for mySky.setJSON paths", () => {
const path = "localhost/cert";
const expected = "efbfbd086e5aefbfbd2fdfb83335efbfbdefbfbdefbfbd623439efbfbdefbfbd5d75efbfbd02efbfbd69efbfbdefbfbd1befbfbd1fefbfbd";
const dataKey = (0, _tweak.deriveDiscoverableTweak)(path);
const input = (0, _string.uint8ArrayToString)(dataKey);
const hash = (0, _string.toHexString)((0, _string.stringToUint8Array)(input));
expect(hash).toEqual(expected);
});
});
describe("trimUriPrefix", () => {

@@ -48,0 +60,0 @@ it("should correctly parse hns prefixed link", () => {

@@ -24,2 +24,3 @@ "use strict";

};
let mock;
describe("uploadFile", () => {

@@ -31,6 +32,8 @@ const url = "".concat(portalUrl, "/skynet/skyfile");

});
let mock;
beforeEach(() => {
mock = new _axiosMockAdapter.default(_axios.default);
mock.onPost(url).replyOnce(200, data);
mock.onHead(portalUrl).replyOnce(200, {}, {
"skynet-portal-api": portalUrl
});
mock.resetHistory();

@@ -45,6 +48,15 @@ });

});
it("should send register onUploadProgress callback if defined", async () => {
it("should set 'credentials' to 'include'", async () => {
await client.uploadFile(file);
expect(mock.history.post.length).toBe(1);
const request = mock.history.post[0];
expect(request.withCredentials).toBeTruthy();
});
it("should register onUploadProgress callback if defined", async () => {
const newPortal = "https://my-portal.net";
const url = "".concat(newPortal, "/skynet/skyfile");
const client = new _index.SkynetClient(newPortal); // Use replyOnce to catch a single request with the new URL.
const client = new _index.SkynetClient(newPortal);
mock.onHead(newPortal).replyOnce(200, {}, {
"skynet-portal-api": portalUrl
}); // Use replyOnce to catch a single request with the new URL.

@@ -119,2 +131,5 @@ mock.onPost(url).replyOnce(200, data);

});
mock.onHead(portalUrl).replyOnce(200, {}, {
"skynet-portal-api": portalUrl
});
mock.onPost("".concat(url, "?file=test")).replyOnce(200, data);

@@ -153,6 +168,8 @@ const query = {

const url = "".concat(portalUrl, "/skynet/skyfile?filename=").concat(filename);
let mock;
beforeEach(() => {
mock = new _axiosMockAdapter.default(_axios.default);
mock.onPost(url).replyOnce(200, data);
mock.onHead(portalUrl).replyOnce(200, {}, {
"skynet-portal-api": portalUrl
});
mock.resetHistory();

@@ -159,0 +176,0 @@ });

"use strict";
var _string = require("./string");
var _url = require("./url");

@@ -7,2 +9,3 @@

const skylink = "XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg";
const skylinkBase32 = "bg06v2tidkir84hg0s1s4t97jaeoaa1jse1svrad657u070c9calq4g";
describe("addUrlQuery", () => {

@@ -34,2 +37,16 @@ it("should return correctly formed URLs with query parameters", () => {

});
describe("getFullDomainUrlForPortal", () => {
const domains = [["dac.hns", "https://dac.hns.siasky.net"], ["dac.hns/", "https://dac.hns.siasky.net"], [skylinkBase32, "https://".concat(skylinkBase32, ".siasky.net")]];
it.each(domains)("domain %s should return correctly formed full URL %s", (domain, fullUrl) => {
const url = (0, _url.getFullDomainUrlForPortal)(portalUrl, domain);
expect(url).toEqual(fullUrl);
});
});
describe("extractDomainForPortal", () => {
const domains = [["dac.hns.siasky.net", "dac.hns"], ["".concat(skylinkBase32, ".siasky.net"), skylinkBase32], ["localhost", "localhost"]];
it.each(domains)("should extract domain %s out of full url %s", (fullDomain, domain) => {
const receivedDomain = (0, _url.extractDomainForPortal)(portalUrl, fullDomain);
expect(receivedDomain).toEqual(domain);
});
});
describe("makeUrl", () => {

@@ -47,2 +64,14 @@ it("should return correctly formed URLs", () => {

});
});
describe("trimPrefix", () => {
it("should trim the prefix with limit if passed", () => {
expect((0, _string.trimPrefix)("//asdf", "/", 1)).toEqual("/asdf");
expect((0, _string.trimPrefix)("//asdf", "/", 0)).toEqual("//asdf");
});
});
describe("trimSuffix", () => {
it("should trim the suffix with limit if passed", () => {
expect((0, _string.trimSuffix)("asdf//", "/", 1)).toEqual("asdf/");
expect((0, _string.trimSuffix)("asdf//", "/", 0)).toEqual("asdf//");
});
});

@@ -11,2 +11,3 @@ "use strict";

exports.stringToUint8Array = stringToUint8Array;
exports.uint8ArrayToString = uint8ArrayToString;
exports.hexToUint8Array = hexToUint8Array;

@@ -16,2 +17,4 @@ exports.isHexString = isHexString;

var _buffer = require("buffer");
/**

@@ -31,2 +34,3 @@ * Removes a prefix from the beginning of the string.

* @param prefix - The prefix to remove.
* @param [limit] - Maximum amount of times to trim. No limit by default.
* @returns - The processed string.

@@ -36,5 +40,13 @@ */

function trimPrefix(str, prefix) {
function trimPrefix(str, prefix, limit) {
while (str.startsWith(prefix)) {
if (limit !== undefined && limit <= 0) {
break;
}
str = str.slice(prefix.length);
if (limit) {
limit -= 1;
}
}

@@ -49,2 +61,3 @@

* @param suffix - The suffix to remove.
* @param [limit] - Maximum amount of times to trim. No limit by default.
* @returns - The processed string.

@@ -54,5 +67,13 @@ */

function trimSuffix(str, suffix) {
function trimSuffix(str, suffix, limit) {
while (str.endsWith(suffix)) {
if (limit !== undefined && limit <= 0) {
break;
}
str = str.substring(0, str.length - suffix.length);
if (limit) {
limit -= 1;
}
}

@@ -101,5 +122,16 @@

return Uint8Array.from(Buffer.from(str));
return Uint8Array.from(_buffer.Buffer.from(str));
}
/**
* Converts a uint8 array to a string.
*
* @param array - The uint8 array to convert.
* @returns - The string.
*/
function uint8ArrayToString(array) {
return _buffer.Buffer.from(array).toString();
}
/**
* Converts a hex encoded string to a uint8 array.

@@ -106,0 +138,0 @@ *

"use strict";
var _tweak = require("../mysky/tweak");
var _skylink = require("./skylink");

@@ -46,2 +48,12 @@

});
describe("stringToUint8Array", () => {
it("Should work for mySky.setJSON paths", () => {
const path = "localhost/cert";
const expected = "efbfbd086e5aefbfbd2fdfb83335efbfbdefbfbdefbfbd623439efbfbdefbfbd5d75efbfbd02efbfbd69efbfbdefbfbd1befbfbd1fefbfbd";
const dataKey = (0, _tweak.deriveDiscoverableTweak)(path);
const input = (0, _string.uint8ArrayToString)(dataKey);
const hash = (0, _string.toHexString)((0, _string.stringToUint8Array)(input));
expect(hash).toEqual(expected);
});
});
describe("trimUriPrefix", () => {

@@ -48,0 +60,0 @@ it("should correctly parse hns prefixed link", () => {

@@ -12,2 +12,6 @@ "use strict";

exports.makeUrl = makeUrl;
exports.getEntryUrlForPortal = getEntryUrlForPortal;
exports.getSkylinkUrlForPortal = getSkylinkUrlForPortal;
exports.getFullDomainUrlForPortal = getFullDomainUrlForPortal;
exports.extractDomainForPortal = extractDomainForPortal;
exports.defaultSkynetPortalUrl = void 0;

@@ -21,2 +25,10 @@

var _registry = require("../registry");
var _crypto = require("../crypto");
var _download = require("../download");
var _skylink = require("./skylink");
const defaultSkynetPortalUrl = "https://siasky.net"; // TODO: This will be smarter. See

@@ -83,2 +95,182 @@ // https://github.com/NebulousLabs/skynet-docs/issues/21.

return args.reduce((acc, cur) => (0, _urlJoin.default)(acc, cur));
}
/**
* Gets the registry entry URL without an initialized client.
*
* @param portalUrl - The portal URL.
* @param publicKey - The user public key.
* @param dataKey - The key of the data to fetch for the given user.
* @param [customOptions] - Additional settings that can optionally be set.
* @returns - The full get entry URL.
* @throws - Will throw if the provided timeout is invalid or the given key is not valid.
*/
function getEntryUrlForPortal(portalUrl, publicKey, dataKey, customOptions) {
/* istanbul ignore next */
if (typeof portalUrl !== "string") {
throw new Error("Expected parameter 'portalUrl' to be type string, was type ".concat(typeof portalUrl));
}
/* istanbul ignore next */
if (typeof publicKey !== "string") {
throw new Error("Expected parameter 'publicKey' to be type string, was type ".concat(typeof publicKey));
}
/* istanbul ignore next */
if (typeof dataKey !== "string") {
throw new Error("Expected parameter 'dataKey' to be type string, was type ".concat(typeof dataKey));
}
const opts = { ..._registry.defaultGetEntryOptions,
...customOptions
};
const timeout = opts.timeout;
if (!Number.isInteger(timeout) || timeout > _registry.MAX_GET_ENTRY_TIMEOUT || timeout < 1) {
throw new Error("Invalid 'timeout' parameter '".concat(timeout, "', needs to be an integer between 1s and ").concat(_registry.MAX_GET_ENTRY_TIMEOUT, "s"));
} // Trim the prefix if it was passed in.
publicKey = (0, _string.trimPrefix)(publicKey, "ed25519:");
if (!(0, _string.isHexString)(publicKey)) {
throw new Error("Given public key '".concat(publicKey, "' is not a valid hex-encoded string or contains an invalid prefix"));
} // We need to hash the data key in order to form the correct URL, as Sia hashes whatever data key we give it.
const dataKeyHash = (0, _string.toHexString)((0, _crypto.hashDataKey)(dataKey));
const query = {
publickey: "ed25519:".concat(publicKey),
datakey: dataKeyHash,
timeout
};
let url = makeUrl(portalUrl, opts.endpointPath);
url = addUrlQuery(url, query);
return url;
}
/**
* Gets the skylink URL without an initialized client.
*
* @param portalUrl - The portal URL.
* @param skylinkUrl - Skylink string. See `downloadFile`.
* @param [customOptions] - Additional settings that can optionally be set.
* @returns - The full URL for the skylink.
* @throws - Will throw if the skylinkUrl does not contain a skylink or if the path option is not a string.
*/
function getSkylinkUrlForPortal(portalUrl, skylinkUrl, customOptions) {
var _opts$query;
/* istanbul ignore next */
if (typeof portalUrl !== "string") {
throw new Error("Expected parameter 'portalUrl' to be type string, was type ".concat(typeof portalUrl));
}
/* istanbul ignore next */
if (typeof skylinkUrl !== "string") {
throw new Error("Expected parameter skylinkUrl to be type string, was type ".concat(typeof skylinkUrl));
}
const opts = { ..._download.defaultDownloadOptions,
...customOptions
};
const query = (_opts$query = opts.query) !== null && _opts$query !== void 0 ? _opts$query : {};
if (opts.download) {
// Set the "attachment" parameter.
query.attachment = true;
}
if (opts.noResponseMetadata) {
// Set the "no-response-metadata" parameter.
query["no-response-metadata"] = true;
} // URL-encode the path.
let path = "";
if (opts.path) {
if (typeof opts.path !== "string") {
throw new Error("opts.path has to be a string, ".concat(typeof opts.path, " provided"));
} // Encode each element of the path separately and join them.
//
// Don't use encodeURI because it does not encode characters such as '?'
// etc. These are allowed as filenames on Skynet and should be encoded so
// they are not treated as URL separators.
path = opts.path.split("/").map(element => encodeURIComponent(element)).join("/");
}
let url;
if (opts.subdomain) {
var _parseSkylink;
// Get the path from the skylink. Use the empty string if not found.
const skylinkPath = (_parseSkylink = (0, _skylink.parseSkylink)(skylinkUrl, {
onlyPath: true
})) !== null && _parseSkylink !== void 0 ? _parseSkylink : ""; // Get just the skylink.
let skylink = (0, _skylink.parseSkylink)(skylinkUrl);
if (skylink === null) {
throw new Error("Could not get skylink out of input '".concat(skylinkUrl, "'"));
} // Convert the skylink (without the path) to base32.
skylink = (0, _skylink.convertSkylinkToBase32)(skylink);
url = addSubdomain(portalUrl, skylink);
url = makeUrl(url, skylinkPath, path);
} else {
// Get the skylink including the path.
const skylink = (0, _skylink.parseSkylink)(skylinkUrl, {
includePath: true
});
if (skylink === null) {
throw new Error("Could not get skylink with path out of input '".concat(skylinkUrl, "'"));
} // Add additional path if passed in.
url = makeUrl(portalUrl, opts.endpointPath, skylink, path);
}
return addUrlQuery(url, query);
}
/**
* Constructs the full URL for the given domain,
* e.g. ("https://siasky.net", "dac.hns") => "https://dac.hns.siasky.net"
*
* @param portalUrl - The portal URL.
* @param domain - Domain.
* @returns - The full URL for the given domain.
*/
function getFullDomainUrlForPortal(portalUrl, domain) {
domain = (0, _string.trimSuffix)(domain, "/");
return addSubdomain(portalUrl, domain);
} // TODO: Expand to also take a fullURL instead of just a fullDomain.
/**
* Extracts the domain from the given portal URL,
* e.g. ("https://siasky.net", "dac.hns.siasky.net") => "dac.hns"
*
* @param portalUrl - The portal URL.
* @param fullUrl - Full URL.
* @returns - The extracted domain.
*/
function extractDomainForPortal(portalUrl, fullDomain) {
const portalUrlObj = new URL(portalUrl);
const portalDomain = portalUrlObj.hostname;
const domain = (0, _string.trimSuffix)(fullDomain, portalDomain, 1);
return (0, _string.trimSuffix)(domain, ".");
}
"use strict";
var _string = require("./string");
var _url = require("./url");

@@ -7,2 +9,3 @@

const skylink = "XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg";
const skylinkBase32 = "bg06v2tidkir84hg0s1s4t97jaeoaa1jse1svrad657u070c9calq4g";
describe("addUrlQuery", () => {

@@ -34,2 +37,16 @@ it("should return correctly formed URLs with query parameters", () => {

});
describe("getFullDomainUrlForPortal", () => {
const domains = [["dac.hns", "https://dac.hns.siasky.net"], ["dac.hns/", "https://dac.hns.siasky.net"], [skylinkBase32, "https://".concat(skylinkBase32, ".siasky.net")]];
it.each(domains)("domain %s should return correctly formed full URL %s", (domain, fullUrl) => {
const url = (0, _url.getFullDomainUrlForPortal)(portalUrl, domain);
expect(url).toEqual(fullUrl);
});
});
describe("extractDomainForPortal", () => {
const domains = [["dac.hns.siasky.net", "dac.hns"], ["".concat(skylinkBase32, ".siasky.net"), skylinkBase32], ["localhost", "localhost"]];
it.each(domains)("should extract domain %s out of full url %s", (fullDomain, domain) => {
const receivedDomain = (0, _url.extractDomainForPortal)(portalUrl, fullDomain);
expect(receivedDomain).toEqual(domain);
});
});
describe("makeUrl", () => {

@@ -47,2 +64,14 @@ it("should return correctly formed URLs", () => {

});
});
describe("trimPrefix", () => {
it("should trim the prefix with limit if passed", () => {
expect((0, _string.trimPrefix)("//asdf", "/", 1)).toEqual("/asdf");
expect((0, _string.trimPrefix)("//asdf", "/", 0)).toEqual("//asdf");
});
});
describe("trimSuffix", () => {
it("should trim the suffix with limit if passed", () => {
expect((0, _string.trimSuffix)("asdf//", "/", 1)).toEqual("asdf/");
expect((0, _string.trimSuffix)("asdf//", "/", 0)).toEqual("asdf//");
});
});
{
"name": "skynet-js",
"version": "3.0.2",
"version": "4.0.0-beta",
"description": "Sia Skynet Javascript Client",

@@ -16,3 +16,3 @@ "main": "dist/index.js",

"build": "rimraf dist && babel src --out-dir dist --extensions .ts --ignore src/**/*.test.ts && tsc --project tsconfig.build.json",
"lint:eslint": "eslint --ext .ts utils src --max-warnings 0",
"lint:eslint": "eslint --ext .ts utils src --max-warnings 30",
"lint:tsc": "tsc",

@@ -30,6 +30,6 @@ "prepublishOnly": "yarn build",

"global": {
"branches": 100,
"functions": 100,
"lines": 100,
"statements": 100
"branches": 70,
"functions": 70,
"lines": 70,
"statements": 70
}

@@ -77,7 +77,9 @@ },

"path-browserify": "^1.0.1",
"post-me": "^0.4.5",
"randombytes": "^2.1.0",
"sjcl": "^1.0.8",
"skynet-mysky-utils": "^0.1.2",
"tweetnacl": "^1.0.3",
"url-join": "^4.0.1",
"url-parse": "^1.4.7"
"url-parse": "1.4.7"
},

@@ -104,3 +106,2 @@ "devDependencies": {

"eslint": "^7.11.0",
"eslint-plugin-compat": "^3.8.0",
"eslint-plugin-jsdoc": "^32.0.0",

@@ -112,4 +113,4 @@ "husky": "^5.0.9",

"rimraf": "^3.0.2",
"typescript": "^4.0.3"
"typescript": "^4.2.3"
}
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc