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

skynet-js

Package Overview
Dependencies
Maintainers
1
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 0.1.0 to 2.0.0

dist/main.js

29

CHANGELOG.md
# Changelog
## [2.0.0]
_Prior version numbers skipped to maintain parity with API._
### Added
- `downloadFileHns`, `openFileHns`, `resolveSkylinkHns`
- `getHnsUrl`, `getHnsresUrl`
- `customFilename` and `customDirname` upload options
### Changed
- `download` and `open` were renamed to `downloadFile` and `openFile`.
- `upload` was renamed to `uploadFile` and the response was changed to only
include a skylink. To obtain the full response as in the old `upload`, use the
new `uploadFileRequest`.
- `getDownloadUrl` has been renamed to `getSkylinkUrl`.
- Connection options can now be passed to the client, in addition to individual
API calls, to be applied to all API calls.
- The `defaultPortalUrl` string has been renamed to `defaultSkynetPortalUrl` and
`defaultPortalUrl` is now a function.
## [0.1.0] - 2020-07-29

@@ -7,3 +29,4 @@

- New `SkynetClient` class that must be initialized to call methods such as `upload` and `download`.
- New `SkynetClient` class that must be initialized to call methods such as
`upload` and `download`.
- New utility helpers such as `getRelativeFilePath` and `defaultPortalUrl`.

@@ -13,2 +36,4 @@

- Most standalone functions are now methods on the `SkynetClient`. Previous code that was calling `upload(...)` instead of `client.upload(...)` will no longer work.
- Most standalone functions are now methods on the `SkynetClient`. Previous code
that was calling `upload(...)` instead of `client.upload(...)` will no longer
work.

@@ -8,11 +8,63 @@ "use strict";

var _axios = _interopRequireDefault(require("axios"));
var _utils = require("./utils.js");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
class SkynetClient {
constructor(portalUrl = null) {
if (portalUrl === null) {
portalUrl = (0, _utils.defaultPortalUrl)();
/**
* The Skynet Client which can be used to access Skynet.
* @constructor
* @param {string} [portalUrl="https://siasky.net"] - The portal URL to use to access Skynet, if specified. To use the default portal while passing custom options, use "".
* @param {Object} [customOptions={}] - Configuration for the client.
* @param {string} [customOptions.method] - HTTP method to use.
* @param {string} [customOptions.APIKey] - Authentication password to use.
* @param {string} [customOptions.customUserAgent=""] - Custom user agent header to set.
* @param {Object} [customOptions.data=null] - Data to send in a POST.
* @param {string} [customOptions.endpointPath=""] - The relative URL path of the portal endpoint to contact.
* @param {string} [customOptions.extraPath=""] - Extra path element to append to the URL.
* @param {Function} [customOptions.onUploadProgress] - Optional callback to track progress.
* @param {Object} [customOptions.query={}] - Query parameters to include in the URl.
*/
constructor(portalUrl = (0, _utils.defaultPortalUrl)(), customOptions = {}) {
this.portalUrl = portalUrl;
this.customOptions = customOptions;
}
/**
* Creates and executes a request.
* @param {Object} config - Configuration for the request. See docs for constructor for the full list of options.
*/
executeRequest(config) {
let url = config.url;
if (!url) {
url = (0, _utils.makeUrl)(this.portalUrl, config.endpointPath, config.extraPath ?? "");
url = (0, _utils.addUrlQuery)(url, config.query);
}
this.portalUrl = portalUrl;
return (0, _axios.default)({
url: url,
method: config.method,
data: config.data,
headers: config.customUserAgent && {
"User-Agent": config.customUserAgent
},
auth: config.APIKey && {
username: "",
password: config.APIKey
},
onUploadProgress: config.onUploadProgress && function ({
loaded,
total
}) {
const progress = loaded / total;
config.onUploadProgress(progress, {
loaded,
total
});
}
});
}

@@ -19,0 +71,0 @@

"use strict";
var _axios = _interopRequireDefault(require("axios"));
var _client = require("./client.js");

@@ -7,17 +9,50 @@

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/* eslint-disable no-unused-vars */
const defaultDownloadOptions = { ...(0, _utils.defaultOptions)("/")
};
const defaultDownloadHnsOptions = { ...(0, _utils.defaultOptions)("/hns")
};
const defaultResolveHnsOptions = { ...(0, _utils.defaultOptions)("/hnsres")
};
/**
* Initiates a download of the content of the skylink within the browser.
* @param {string} skylink - 46 character skylink.
* @param {Object} [customOptions={}] - Additional settings that can optionally be set.
* @param {string} [customOptions.endpointPath="/"] - The relative URL path of the portal endpoint to contact.
*/
_client.SkynetClient.prototype.download = function (skylink, customOptions = {}) {
_client.SkynetClient.prototype.downloadFile = function (skylink, customOptions = {}) {
const opts = { ...defaultDownloadOptions,
...this.customOptions,
...customOptions,
download: true
};
const url = this.getDownloadUrl(skylink, opts);
window.open(url, "_blank");
const url = this.getSkylinkUrl(skylink, opts); // Download the url.
window.location = url;
};
/**
* Initiates a download of the content of the skylink at the Handshake domain.
* @param {string} domain - Handshake domain.
* @param {Object} [customOptions={}] - Additional settings that can optionally be set.
* @param {string} [customOptions.endpointPath="/hns"] - The relative URL path of the portal endpoint to contact.
*/
_client.SkynetClient.prototype.getDownloadUrl = function (skylink, customOptions = {}) {
_client.SkynetClient.prototype.downloadFileHns = async function (domain, customOptions = {}) {
const opts = { ...defaultDownloadHnsOptions,
...this.customOptions,
...customOptions,
download: true
};
const url = this.getHnsUrl(domain, opts); // Download the url.
window.location = url;
};
_client.SkynetClient.prototype.getSkylinkUrl = function (skylink, customOptions = {}) {
const opts = { ...defaultDownloadOptions,
...this.customOptions,
...customOptions

@@ -28,7 +63,32 @@ };

} : {};
return (0, _utils.makeUrlWithSkylink)(this.portalUrl, opts.endpointPath, skylink, query);
const url = (0, _utils.makeUrl)(this.portalUrl, opts.endpointPath, (0, _utils.parseSkylink)(skylink));
return (0, _utils.addUrlQuery)(url, query);
};
_client.SkynetClient.prototype.metadata = function (skylink, customOptions = {}) {
_client.SkynetClient.prototype.getHnsUrl = function (domain, customOptions = {}) {
const opts = { ...defaultDownloadHnsOptions,
...this.customOptions,
...customOptions
};
const query = opts.download ? {
attachment: true
} : {};
const url = (0, _utils.makeUrl)(this.portalUrl, opts.endpointPath, (0, _utils.trimUriPrefix)(domain, _utils.uriHandshakePrefix));
return (0, _utils.addUrlQuery)(url, query);
};
_client.SkynetClient.prototype.getHnsresUrl = function (domain, customOptions = {}) {
const opts = { ...defaultResolveHnsOptions,
...this.customOptions,
...customOptions
};
const query = opts.download ? {
attachment: true
} : {};
return (0, _utils.makeUrl)(this.portalUrl, opts.endpointPath, (0, _utils.trimUriPrefix)(domain, _utils.uriHandshakeResolverPrefix));
};
_client.SkynetClient.prototype.getMetadata = async function (skylink, customOptions = {}) {
const opts = { ...defaultDownloadOptions,
...this.customOptions,
...customOptions

@@ -38,9 +98,54 @@ };

};
/**
* Opens the content of the skylink within the browser.
* @param {string} skylink - 46 character skylink.
* @param {Object} [customOptions={}] - Additional settings that can optionally be set.
* @param {string} [customOptions.endpointPath="/"] - The relative URL path of the portal endpoint to contact.
*/
_client.SkynetClient.prototype.open = function (skylink, customOptions = {}) {
_client.SkynetClient.prototype.openFile = function (skylink, customOptions = {}) {
const opts = { ...defaultDownloadOptions,
...this.customOptions,
...customOptions
};
const url = (0, _utils.makeUrlWithSkylink)(this.portalUrl, opts.endpointPath, skylink);
const url = this.getSkylinkUrl(skylink, opts);
window.open(url, "_blank");
};
/**
* Opens the content of the skylink from the given Handshake domain within the browser.
* @param {string} domain - Handshake domain.
* @param {Object} [customOptions={}] - Additional settings that can optionally be set.
* @param {string} [customOptions.endpointPath="/hns"] - The relative URL path of the portal endpoint to contact.
*/
_client.SkynetClient.prototype.openFileHns = async function (domain, customOptions = {}) {
const opts = { ...defaultDownloadHnsOptions,
...this.customOptions,
...customOptions
};
const url = this.getHnsUrl(domain, opts); // Open the url in a new tab.
window.open(url, "_blank");
};
/**
* @param {string} domain - Handshake resolver domain.
* @param {Object} [customOptions={}] - Additional settings that can optionally be set.
* @param {string} [customOptions.endpointPath="/hnsres"] - The relative URL path of the portal endpoint to contact.
*/
_client.SkynetClient.prototype.resolveHns = async function (domain, customOptions = {}) {
const opts = { ...defaultResolveHnsOptions,
...this.customOptions,
...customOptions
};
const url = this.getHnsresUrl(domain, opts); // Get the txt record from the hnsres domain on the portal.
const response = await this.executeRequest({ ...opts,
method: "get",
url
});
return response.data;
};
"use strict";
var _axios = _interopRequireDefault(require("axios"));
var _axiosMockAdapter = _interopRequireDefault(require("axios-mock-adapter"));
var _index = require("./index");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const mock = new _axiosMockAdapter.default(_axios.default);
const portalUrl = _index.defaultSkynetPortalUrl;
const hnsLink = "foo";
const hnsUrl = `${portalUrl}/hns/${hnsLink}`;
const hnsresUrl = `${portalUrl}/hnsres/${hnsLink}`;
const client = new _index.SkynetClient(portalUrl);
const skylink = "XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg";
const validSkylinkVariations = [skylink, `sia:${skylink}`, `sia://${skylink}`, `${portalUrl}/${skylink}`, `${portalUrl}/${skylink}/foo/bar`, `${portalUrl}/${skylink}?foo=bar`];
describe("download", () => {
const validHnsLinkVariations = [hnsLink, `hns:${hnsLink}`, `hns://${hnsLink}`];
const validHnsresLinkVariations = [hnsLink, `hnsres:${hnsLink}`, `hnsres://${hnsLink}`];
describe("downloadFile", () => {
it("should call window.open with a download url with attachment set", () => {

@@ -14,27 +26,47 @@ const windowOpen = jest.spyOn(window, "open").mockImplementation();

windowOpen.mockReset();
client.download(input);
expect(windowOpen).toHaveBeenCalledTimes(1);
expect(windowOpen).toHaveBeenCalledWith(`${portalUrl}/${skylink}?attachment=true`, "_blank");
client.downloadFile(input);
expect(mock.history.get.length).toBe(0);
});
});
});
describe("getDownloadUrl", () => {
it("should return correctly formed download URL", () => {
validSkylinkVariations.forEach(input => {
expect(client.getDownloadUrl(input)).toEqual(`${portalUrl}/${skylink}`);
describe("getHnsUrl", () => {
it("should return correctly formed hns URL", () => {
validHnsLinkVariations.forEach(input => {
expect(client.getHnsUrl(input)).toEqual(hnsUrl);
});
});
it("should return correctly formed url with forced download", () => {
const url = client.getDownloadUrl(skylink, {
it("should return correctly formed hns URL with forced download", () => {
const url = client.getHnsUrl(hnsLink, {
download: true
});
expect(url).toEqual(`${portalUrl}/${skylink}?attachment=true`);
expect(url).toEqual(`${hnsUrl}?attachment=true`);
});
});
describe("getHnsresUrl", () => {
it("should return correctly formed hnsres URL", () => {
validHnsresLinkVariations.forEach(input => {
expect(client.getHnsresUrl(input)).toEqual(hnsresUrl);
});
});
});
describe("getSkylinkUrl", () => {
it("should return correctly formed skylink URL", () => {
validSkylinkVariations.forEach(input => {
expect(client.getSkylinkUrl(input)).toEqual(`${portalUrl}/${skylink}`);
});
});
it("should return correctly formed URL with forced download", () => {
const url = client.getSkylinkUrl(skylink, {
download: true,
endpointPath: "skynet/skylink"
});
expect(url).toEqual(`${portalUrl}/skynet/skylink/${skylink}?attachment=true`);
});
});
describe("open", () => {
it("should call window.open with a download url", () => {
it("should call window.openFile", () => {
const windowOpen = jest.spyOn(window, "open").mockImplementation();
validSkylinkVariations.forEach(input => {
windowOpen.mockReset();
client.open(input);
client.openFile(input);
expect(windowOpen).toHaveBeenCalledTimes(1);

@@ -44,2 +76,44 @@ expect(windowOpen).toHaveBeenCalledWith(`${portalUrl}/${skylink}`, "_blank");

});
});
describe("downloadFileHns", () => {
it("should set domain with the portal and hns link and then call window.openFile with attachment set", async () => {
const windowOpen = jest.spyOn(window, "open").mockImplementation();
for (const input of validHnsLinkVariations) {
mock.resetHistory();
windowOpen.mockReset();
await client.downloadFileHns(input);
expect(mock.history.get.length).toBe(0);
}
});
});
describe("openFileHns", () => {
const hnsUrl = `${portalUrl}/hns/${hnsLink}`;
it("should set domain with the portal and hns link and then call window.openFile", async () => {
const windowOpen = jest.spyOn(window, "open").mockImplementation();
for (const input of validHnsLinkVariations) {
mock.resetHistory();
windowOpen.mockReset();
await client.openFileHns(input);
expect(mock.history.get.length).toBe(0);
expect(windowOpen).toHaveBeenCalledTimes(1);
expect(windowOpen).toHaveBeenCalledWith(hnsUrl, "_blank");
}
});
});
describe("resolveHns", () => {
beforeEach(() => {
mock.onGet(hnsresUrl).reply(200, {
skylink: 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);
}
});
});

12

dist/encryption.js
"use strict";
var _client = require("./client.js");
var _utils = require("./utils.js");

@@ -15,20 +17,20 @@

_utils.SkynetClient.prototype.addSkykey = async function (skykey, customOptions = {}) {
_client.SkynetClient.prototype.addSkykey = async function (skykey, customOptions = {}) {
throw new Error("Unimplemented");
};
_utils.SkynetClient.prototype.createSkykey = async function (skykeyName, skykeyType, customOptions = {}) {
_client.SkynetClient.prototype.createSkykey = async function (skykeyName, skykeyType, customOptions = {}) {
throw new Error("Unimplemented");
};
_utils.SkynetClient.prototype.getSkykeyById = async function (skykeyId, customOptions = {}) {
_client.SkynetClient.prototype.getSkykeyById = async function (skykeyId, customOptions = {}) {
throw new Error("Unimplemented");
};
_utils.SkynetClient.prototype.getSkykeyByName = async function (skykeyName, customOptions = {}) {
_client.SkynetClient.prototype.getSkykeyByName = async function (skykeyName, customOptions = {}) {
throw new Error("Unimplemented");
};
_utils.SkynetClient.prototype.getSkykeys = async function getSkykeys(customOptions = {}) {
_client.SkynetClient.prototype.getSkykeys = async function getSkykeys(customOptions = {}) {
throw new Error("Unimplemented");
};

@@ -42,2 +42,20 @@ "use strict";

});
Object.defineProperty(exports, "uriHandshakePrefix", {
enumerable: true,
get: function () {
return _utils.uriHandshakePrefix;
}
});
Object.defineProperty(exports, "uriHandshakeResolverPrefix", {
enumerable: true,
get: function () {
return _utils.uriHandshakeResolverPrefix;
}
});
Object.defineProperty(exports, "uriSkynetPrefix", {
enumerable: true,
get: function () {
return _utils.uriSkynetPrefix;
}
});

@@ -48,4 +66,6 @@ var _client = require("./client.js");

require("./encryption.js");
require("./upload.js");
var _utils = require("./utils.js");

@@ -9,9 +9,23 @@ "use strict";

expect(client).toHaveProperty("download");
expect(client).toHaveProperty("getDownloadUrl");
expect(client).toHaveProperty("open"); // Upload
expect(client).toHaveProperty("downloadFile");
expect(client).toHaveProperty("downloadFileHns");
expect(client).toHaveProperty("getHnsUrl");
expect(client).toHaveProperty("getHnsresUrl");
expect(client).toHaveProperty("getSkylinkUrl");
expect(client).toHaveProperty("getMetadata");
expect(client).toHaveProperty("openFile");
expect(client).toHaveProperty("openFileHns");
expect(client).toHaveProperty("resolveHns"); // Encryption
expect(client).toHaveProperty("upload");
expect(client).toHaveProperty("addSkykey");
expect(client).toHaveProperty("createSkykey");
expect(client).toHaveProperty("getSkykeyById");
expect(client).toHaveProperty("getSkykeyByName");
expect(client).toHaveProperty("getSkykeys"); // Upload
expect(client).toHaveProperty("uploadFile");
expect(client).toHaveProperty("uploadFileRequest");
expect(client).toHaveProperty("uploadDirectory");
expect(client).toHaveProperty("uploadDirectoryRequest");
});
});
"use strict";
var _axios = _interopRequireDefault(require("axios"));
var _client = require("./client.js");

@@ -9,31 +7,27 @@

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const defaultUploadOptions = { ...(0, _utils.defaultOptions)("/skynet/skyfile"),
portalFileFieldname: "file",
portalDirectoryFileFieldname: "files[]" // TODO:
// customFilename: "",
portalDirectoryFileFieldname: "files[]",
customFilename: ""
};
_client.SkynetClient.prototype.uploadFile = async function (file, customOptions = {}) {
const response = await this.uploadFileRequest(file, customOptions);
return `${_utils.uriSkynetPrefix}${response.skylink}`;
};
_client.SkynetClient.prototype.upload = async function (file, customOptions = {}) {
_client.SkynetClient.prototype.uploadFileRequest = async function (file, customOptions = {}) {
const opts = { ...defaultUploadOptions,
...this.customOptions,
...customOptions
};
const formData = new FormData();
formData.append(opts.portalFileFieldname, ensureFileObjectConsistency(file));
const url = (0, _utils.makeUrl)(this.portalUrl, opts.endpointPath);
file = ensureFileObjectConsistency(file);
const filename = opts.customFilename ? opts.customFilename : "";
formData.append(opts.portalFileFieldname, file, filename);
const {
data
} = await _axios.default.post(url, formData, opts.onUploadProgress && {
onUploadProgress: ({
loaded,
total
}) => {
const progress = loaded / total;
opts.onUploadProgress(progress, {
loaded,
total
});
}
} = await this.executeRequest({ ...opts,
method: "post",
data: formData
});

@@ -47,4 +41,11 @@ return data;

* @param {Object} [customOptions={}] - Additional settings that can optionally be set.
* @param {string} [config.APIKey] - Authentication password to use.
* @param {string} [config.customUserAgent=""] - Custom user agent header to set.
* @param {string} [customOptions.endpointPath="/skynet/skyfile"] - The relative URL path of the portal endpoint to contact.
* @param {Function} [config.onUploadProgress] - Optional callback to track progress.
* @param {string} [customOptions.portalDirectoryfilefieldname="files[]"] - The fieldName for directory files on the portal.
* @returns {Object} data - The returned data.
* @returns {string} data.skylink - The returned skylink.
* @returns {string} data.merkleroot - The hash that is encoded into the skylink.
* @returns {number} data.bitfield - The bitfield that gets encoded into the skylink.
*/

@@ -54,3 +55,9 @@

_client.SkynetClient.prototype.uploadDirectory = async function (directory, filename, customOptions = {}) {
const response = await this.uploadDirectoryRequest(directory, filename, customOptions);
return `${_utils.uriSkynetPrefix}${response.skylink}`;
};
_client.SkynetClient.prototype.uploadDirectoryRequest = async function (directory, filename, customOptions = {}) {
const opts = { ...defaultUploadOptions,
...this.customOptions,
...customOptions

@@ -60,19 +67,12 @@ };

Object.entries(directory).forEach(([path, file]) => {
formData.append(opts.portalDirectoryFileFieldname, ensureFileObjectConsistency(file), path);
file = ensureFileObjectConsistency(file);
formData.append(opts.portalDirectoryFileFieldname, file, path);
});
const url = (0, _utils.makeUrl)(this.portalUrl, opts.endpointPath, {
filename
});
const {
data
} = await _axios.default.post(url, formData, opts.onUploadProgress && {
onUploadProgress: ({
loaded,
total
}) => {
const progress = loaded / total;
opts.onUploadProgress(progress, {
loaded,
total
});
} = await this.executeRequest({ ...opts,
method: "post",
data: formData,
query: {
filename
}

@@ -79,0 +79,0 @@ });

@@ -5,11 +5,17 @@ "use strict";

var _axiosMockAdapter = _interopRequireDefault(require("axios-mock-adapter"));
var _index = require("./index");
var _test_utils = require("./test_utils.js");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
jest.mock("axios");
const mock = new _axiosMockAdapter.default(_axios.default);
const portalUrl = _index.defaultSkynetPortalUrl;
const client = new _index.SkynetClient(portalUrl);
const skylink = "XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg";
const sialink = `${_index.uriSkynetPrefix}${skylink}`;
describe("uploadFile", () => {
const url = `${portalUrl}/skynet/skyfile`;
const filename = "bar.txt";

@@ -20,74 +26,112 @@ const file = new File(["foo"], filename, {

beforeEach(() => {
_axios.default.post.mockResolvedValue({
data: {
skylink
}
mock.onPost(url).reply(200, {
skylink: skylink
});
mock.resetHistory();
});
it("should send post request with FormData", () => {
client.upload(file, {});
expect(_axios.default.post).toHaveBeenCalledWith(`${portalUrl}/skynet/skyfile`, expect.any(FormData), // TODO: Inspect data contents.
undefined);
it("should send formdata with file", async () => {
const data = await client.uploadFile(file);
expect(mock.history.post.length).toBe(1);
const request = mock.history.post[0];
await (0, _test_utils.compareFormData)(request.data, [["file", "foo", filename]]);
expect(data).toEqual(sialink);
});
it("should send register onUploadProgress callback if defined", () => {
it("should send register onUploadProgress callback if defined", async () => {
const newPortal = "https://my-portal.net";
const client = new _index.SkynetClient(newPortal);
client.upload(file, {
const url = `${newPortal}/skynet/skyfile`;
const client = new _index.SkynetClient(newPortal); // Use replyOnce to catch a single request with the new URL.
mock.onPost(url).replyOnce(200, {
skylink: skylink
});
const data = await client.uploadFile(file, {
onUploadProgress: jest.fn()
});
expect(_axios.default.post).toHaveBeenCalledWith(`${newPortal}/skynet/skyfile`, expect.any(FormData), // TODO: Inspect data contents.
{
onUploadProgress: expect.any(Function)
expect(mock.history.post.length).toBe(1);
const request = mock.history.post[0];
expect(request.onUploadProgress).toEqual(expect.any(Function));
await (0, _test_utils.compareFormData)(request.data, [["file", "foo", filename]]);
expect(data).toEqual(sialink);
});
it("should use custom filename if provided", async () => {
const data = await client.uploadFile(file, {
customFilename: "testname"
});
expect(mock.history.post.length).toBe(1);
const request = mock.history.post[0];
await (0, _test_utils.compareFormData)(request.data, [["file", "foo", "testname"]]);
expect(data).toEqual(sialink);
});
it("should send base-64 authentication password if provided", () => {
client.upload(file, {
it("should send base-64 authentication password if provided", async () => {
const data = await client.uploadFile(file, {
APIKey: "foobar"
});
expect(_axios.default.post).toHaveBeenCalledWith(`${portalUrl}/skynet/skyfile`, expect.any(FormData), // TODO: Inspect data contents.
undefined);
expect(mock.history.post.length).toBe(1);
const request = mock.history.post[0];
expect(request.auth).toEqual({
username: "",
password: "foobar"
});
await (0, _test_utils.compareFormData)(request.data, [["file", "foo", filename]]);
expect(data).toEqual(sialink);
});
it("should return skylink on success", async () => {
const data = await client.upload(file);
expect(data).toEqual({
skylink
it("should send custom user agent if defined", async () => {
const client = new _index.SkynetClient(portalUrl, {
customUserAgent: "Sia-Agent"
});
const data = await client.uploadFile(file);
expect(mock.history.post.length).toBe(1);
const request = mock.history.post[0];
expect(request.headers["User-Agent"]).toEqual("Sia-Agent"); // Check that other headers weren't altered.
expect(request.headers["Content-Type"]).toEqual("application/x-www-form-urlencoded");
await (0, _test_utils.compareFormData)(request.data, [["file", "foo", filename]]);
expect(data).toEqual(sialink);
});
it("Should use user agent set in options to function", async () => {
const client = new _index.SkynetClient(portalUrl, {
customUserAgent: "Sia-Agent"
});
const data = await client.uploadFile(file, {
customUserAgent: "Sia-Agent-2"
});
expect(mock.history.post.length).toBe(1);
const request = mock.history.post[0];
expect(request.headers["User-Agent"]).toEqual("Sia-Agent-2"); // Check that other headers weren't altered.
expect(request.headers["Content-Type"]).toEqual("application/x-www-form-urlencoded");
await (0, _test_utils.compareFormData)(request.data, [["file", "foo", filename]]);
expect(data).toEqual(sialink);
});
});
describe("uploadDirectory", () => {
const blob = new Blob([], {
type: "image/jpeg"
});
const filename = "i-am-root";
const directory = {
"i-am-not/file1.jpeg": new File([blob], "i-am-not/file1.jpeg"),
"i-am-not/file2.jpeg": new File([blob], "i-am-not/file2.jpeg"),
"i-am-not/me-neither/file3.jpeg": new File([blob], "i-am-not/me-neither/file3.jpeg")
"i-am-not/file1.jpeg": new File(["foo1"], "i-am-not/file1.jpeg"),
"i-am-not/file2.jpeg": new File(["foo2"], "i-am-not/file2.jpeg"),
"i-am-not/me-neither/file3.jpeg": new File(["foo3"], "i-am-not/me-neither/file3.jpeg")
};
const url = `${portalUrl}/skynet/skyfile?filename=${filename}`;
beforeEach(() => {
_axios.default.post.mockResolvedValue({
data: {
skylink
}
mock.onPost(url).reply(200, {
skylink: skylink
});
mock.resetHistory();
});
it("should send post request with FormData", () => {
client.uploadDirectory(directory, filename);
expect(_axios.default.post).toHaveBeenCalledWith(`${portalUrl}/skynet/skyfile?filename=${filename}`, expect.any(FormData), // TODO: Inspect data contents.
undefined);
it("should send formdata with files", async () => {
const data = await client.uploadDirectory(directory, filename);
expect(mock.history.post.length).toBe(1);
const request = mock.history.post[0];
await (0, _test_utils.compareFormData)(request.data, [["files[]", "foo1", "i-am-not/file1.jpeg"], ["files[]", "foo2", "i-am-not/file2.jpeg"], ["files[]", "foo3", "i-am-not/me-neither/file3.jpeg"]]);
expect(data).toEqual(sialink);
});
it("should send register onUploadProgress callback if defined", () => {
client.uploadDirectory(directory, filename, {
it("should send register onUploadProgress callback if defined", async () => {
const data = await client.uploadDirectory(directory, filename, {
onUploadProgress: jest.fn()
});
expect(_axios.default.post).toHaveBeenCalledWith(`${portalUrl}/skynet/skyfile?filename=${filename}`, expect.any(FormData), {
onUploadProgress: expect.any(Function)
});
expect(mock.history.post.length).toBe(1);
const request = mock.history.post[0];
expect(request.onUploadProgress).toEqual(expect.any(Function));
expect(data).toEqual(sialink);
});
it("should return single skylink on success", async () => {
const data = await client.uploadDirectory(directory, filename);
expect(data).toEqual({
skylink
});
});
});

@@ -6,10 +6,11 @@ "use strict";

});
exports.addUrlQuery = addUrlQuery;
exports.defaultOptions = defaultOptions;
exports.defaultPortalUrl = defaultPortalUrl;
exports.defaultOptions = defaultOptions;
exports.getRelativeFilePath = getRelativeFilePath;
exports.getRootDirectory = getRootDirectory;
exports.makeUrl = makeUrl;
exports.makeUrlWithSkylink = makeUrlWithSkylink;
exports.parseSkylink = parseSkylink;
exports.defaultSkynetPortalUrl = void 0;
exports.trimUriPrefix = trimUriPrefix;
exports.uriSkynetPrefix = exports.uriHandshakeResolverPrefix = exports.uriHandshakePrefix = exports.defaultSkynetPortalUrl = void 0;

@@ -20,2 +21,4 @@ var _pathBrowserify = _interopRequireDefault(require("path-browserify"));

var _urlJoin = _interopRequireDefault(require("url-join"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

@@ -26,6 +29,13 @@

exports.defaultSkynetPortalUrl = defaultSkynetPortalUrl;
const uriHandshakePrefix = "hns:";
exports.uriHandshakePrefix = uriHandshakePrefix;
const uriHandshakeResolverPrefix = "hnsres:";
exports.uriHandshakeResolverPrefix = uriHandshakeResolverPrefix;
const uriSkynetPrefix = "sia:";
exports.uriSkynetPrefix = uriSkynetPrefix;
function defaultPortalUrl() {
var url = new URL(window.location.href);
return url.href.substring(0, url.href.indexOf(url.pathname));
function addUrlQuery(url, query) {
const parsed = (0, _urlParse.default)(url);
parsed.set("query", query);
return parsed.toString();
}

@@ -35,25 +45,15 @@

return {
endpointPath: endpointPath // TODO:
// APIKey: "",
// customUserAgent: "",
endpointPath: endpointPath,
APIKey: "",
customUserAgent: ""
};
} // TODO: Use this to simplify creating requests. Needs to be tested.
// export function executeRequest(portalUrl, method, opts, query, data = {}) {
// const url = makeUrl(portalUrl, opts.endpointPath, query);
// return axios({
// method: method,
// url: url,
// data: data,
// auth: opts.APIKey && {username: "", password: opts.APIKey },
// onUploadProgress: opts.onUploadProgress && {
// onUploadProgress: ({ loaded, total }) => {
// const progress = loaded / total;
// opts.onUploadProgress(progress, { loaded, total });
// },
// }
// });
// }
} // TODO: This will be smarter. See
// https://github.com/NebulousLabs/skynet-docs/issues/21.
function defaultPortalUrl() {
var url = new URL(window.location.href);
return url.href.substring(0, url.href.indexOf(url.pathname));
}
function getFilePath(file) {

@@ -87,13 +87,13 @@ return file.webkitRelativePath || file.path || file.name;

}
/**
* Properly joins paths together to create a URL. Takes a variable number of
* arguments.
* @returns {string} url - The URL.
*/
function makeUrl(portalUrl, pathname, query = {}) {
const parsed = (0, _urlParse.default)(portalUrl);
parsed.set("pathname", pathname);
parsed.set("query", query);
return parsed.toString();
}
function makeUrlWithSkylink(portalUrl, endpointPath, skylink, query = {}) {
const parsedSkylink = parseSkylink(skylink);
return makeUrl(portalUrl, _pathBrowserify.default.posix.join(endpointPath, parsedSkylink), query);
function makeUrl(...args) {
return args.reduce(function (acc, cur) {
return (0, _urlJoin.default)(acc, cur);
});
}

@@ -103,7 +103,6 @@

const SKYLINK_DIRECT_REGEX = new RegExp(`^${SKYLINK_MATCHER}$`);
const SKYLINK_SIA_PREFIXED_REGEX = new RegExp(`^sia:(?://)?${SKYLINK_MATCHER}$`);
const SKYLINK_PATHNAME_REGEX = new RegExp(`^/${SKYLINK_MATCHER}`);
const SKYLINK_REGEXP_MATCH_POSITION = 1;
function parseSkylink(skylink = "") {
function parseSkylink(skylink) {
if (typeof skylink !== "string") throw new Error(`Skylink has to be a string, ${typeof skylink} provided`); // check for direct skylink match

@@ -116,4 +115,3 @@

const matchSiaPrefixed = skylink.match(SKYLINK_SIA_PREFIXED_REGEX);
if (matchSiaPrefixed) return matchSiaPrefixed[SKYLINK_REGEXP_MATCH_POSITION]; // check for skylink passed in an url and extract it
skylink = trimUriPrefix(skylink, uriSkynetPrefix); // check for skylink passed in an url and extract it
// example: https://siasky.net/XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg

@@ -125,2 +123,18 @@

throw new Error(`Could not extract skylink from '${skylink}'`);
}
function trimUriPrefix(str, prefix) {
const longPrefix = `${prefix}//`;
if (str.startsWith(longPrefix)) {
// longPrefix is exactly at the beginning
return str.slice(longPrefix.length);
}
if (str.startsWith(prefix)) {
// else prefix is exactly at the beginning
return str.slice(prefix.length);
}
return str;
}

@@ -7,22 +7,46 @@ "use strict";

const skylink = "XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg";
const validSkylinkVariations = [skylink, `sia:${skylink}`, `sia://${skylink}`, `${portalUrl}/${skylink}`, `${portalUrl}/${skylink}/foo/bar`, `${portalUrl}/${skylink}?foo=bar`];
const hnsLink = "doesn";
const hnsresLink = "doesn";
describe("addUrlQuery", () => {
it("should return correctly formed URLs with query parameters", () => {
expect((0, _utils.addUrlQuery)(portalUrl, {
filename: "test"
})).toEqual(`${portalUrl}?filename=test`);
expect((0, _utils.addUrlQuery)(`${portalUrl}/path/`, {
download: true
})).toEqual(`${portalUrl}/path/?download=true`);
expect((0, _utils.addUrlQuery)(`${portalUrl}/skynet/`, {
foo: 1,
bar: 2
})).toEqual(`${portalUrl}/skynet/?foo=1&bar=2`);
expect((0, _utils.addUrlQuery)(`${portalUrl}/`, {
attachment: true
})).toEqual(`${portalUrl}/?attachment=true`);
});
});
describe("makeUrl", () => {
it("should return correctly formed URLs", () => {
expect((0, _utils.makeUrl)(portalUrl, "/")).toEqual(`${portalUrl}/`);
expect((0, _utils.makeUrl)(portalUrl, "/", {
attachment: true
})).toEqual(`${portalUrl}/?attachment=true`);
expect((0, _utils.makeUrl)(portalUrl, "/skynet")).toEqual(`${portalUrl}/skynet`);
expect((0, _utils.makeUrl)(portalUrl, "/skynet/")).toEqual(`${portalUrl}/skynet/`);
expect((0, _utils.makeUrl)(portalUrl, "/skynet/", {
foo: 1,
bar: 2
})).toEqual(`${portalUrl}/skynet/?foo=1&bar=2`);
expect((0, _utils.makeUrlWithSkylink)(portalUrl, "/", skylink)).toEqual(`${portalUrl}/${skylink}`);
expect((0, _utils.makeUrlWithSkylink)(portalUrl, "/skynet", skylink)).toEqual(`${portalUrl}/skynet/${skylink}`);
expect((0, _utils.makeUrlWithSkylink)(portalUrl, "/skynet/", skylink)).toEqual(`${portalUrl}/skynet/${skylink}`);
expect((0, _utils.makeUrl)(portalUrl, "/", skylink)).toEqual(`${portalUrl}/${skylink}`);
expect((0, _utils.makeUrl)(portalUrl, "/skynet", skylink)).toEqual(`${portalUrl}/skynet/${skylink}`);
expect((0, _utils.makeUrl)(portalUrl, "//skynet/", skylink)).toEqual(`${portalUrl}/skynet/${skylink}`);
});
});
describe("trimUriPrefix", () => {
it("should correctly parse hns prefixed link", () => {
const validHnsLinkVariations = [hnsLink, `hns:${hnsLink}`, `hns://${hnsLink}`];
const validHnsresLinkVariations = [hnsresLink, `hnsres:${hnsresLink}`, `hnsres://${hnsresLink}`];
validHnsLinkVariations.forEach(input => {
expect((0, _utils.trimUriPrefix)(input, _utils.uriHandshakePrefix)).toEqual(hnsLink);
});
validHnsresLinkVariations.forEach(input => {
expect((0, _utils.trimUriPrefix)(input, _utils.uriHandshakeResolverPrefix)).toEqual(hnsresLink);
});
});
});
describe("parseSkylink", () => {
it("should correctly parse skylink out of different strings", () => {
const validSkylinkVariations = [skylink, `sia:${skylink}`, `sia://${skylink}`, `${portalUrl}/${skylink}`, `${portalUrl}/${skylink}/foo/bar`, `${portalUrl}/${skylink}?foo=bar`];
validSkylinkVariations.forEach(input => {

@@ -33,3 +57,3 @@ expect((0, _utils.parseSkylink)(input)).toEqual(skylink);

it("should throw on invalid skylink", () => {
expect(() => (0, _utils.parseSkylink)()).toThrowError("Could not extract skylink from ''");
expect(() => (0, _utils.parseSkylink)()).toThrowError("Skylink has to be a string, undefined provided");
expect(() => (0, _utils.parseSkylink)(123)).toThrowError("Skylink has to be a string, number provided");

@@ -36,0 +60,0 @@ expect(() => (0, _utils.parseSkylink)("123")).toThrowError("Could not extract skylink from '123'");

{
"name": "skynet-js",
"version": "0.1.0",
"version": "2.0.0",
"description": "Sia Skynet Javascript Client",

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

"scripts": {
"test": "jest",
"test": "jest --runInBand",
"prepublishOnly": "babel src --out-dir dist"

@@ -44,4 +44,5 @@ },

"dependencies": {
"axios": "^0.19.2",
"axios": "^0.20.0",
"path-browserify": "^1.0.1",
"url-join": "^4.0.1",
"url-parse": "^1.4.7"

@@ -53,2 +54,3 @@ },

"@babel/preset-env": "^7.9.0",
"axios-mock-adapter": "^1.18.2",
"babel-jest": "^26.0.1",

@@ -55,0 +57,0 @@ "eslint": "^7.1.0",

@@ -25,166 +25,8 @@ # skynet-js - Javascript Sia Skynet Client

## Docs
## Documentation
### Using SkynetClient
For documentation complete with examples, please see [the Skynet SDK docs](https://nebulouslabs.github.io/skynet-docs/?javascript--browser#introduction).
Client implements all the standalone functions as methods with bound `portalUrl` so you don't need to repeat it every time.
### Browser Utility Functions
`portalUrl` (string) - Optional portal url. If not specified, will try to use the current portal that the sky app is running inside of.
```javascript
import { SkynetClient } from "skynet-js";
const client = new SkynetClient("https://siasky.net");
```
Calling `SkynetClient` without parameters will use the URL of the current portal that is running the skapp (sky app).
### async upload(file, [options])
Use the client to upload `file` contents.
`file` (File) - The file to upload.
`options.APIKey` (string) - Optional API key password for authentication.
`options.onUploadProgress` (function) - Optional callback to track progress.
Returns a promise that resolves with a `{ skylink }` or throws `error` on failure.
```javascript
import { SkynetClient } from "skynet-js";
const onUploadProgress = (progress, { loaded, total }) => {
console.info(`Progress ${Math.round(progress * 100)}%`);
};
async function uploadExample() {
try {
const client = new SkynetClient();
const { skylink } = await client.upload(file, { onUploadProgress });
} catch (error) {
console.log(error);
}
}
```
With authentication:
```javascript
import { SkynetClient } from "skynet-js";
async function authenticationExample() {
try {
const client = new SkynetClient("https://my-portal.net");
const { skylink } = await client.upload(file, { APIKey: "foobar" });
} catch (error) {
console.log(error);
}
}
```
### async uploadDirectory(directory, filename, [options])
Use the client to upload `directory` contents as a `filename`.
`directory` (Object) - Directory map `{ "file1.jpeg": <File>, "subdirectory/file2.jpeg": <File> }`
`filename` (string) - Output file name (directory name).
`options.onUploadProgress` (function) - Optional callback to track progress.
Returns a promise that resolves with a `{ skylink }` or throws `error` on failure.
#### Browser example
```javascript
import { getRelativeFilePath, getRootDirectory, SkynetClient } from "skynet-js";
// Assume we have a list of files from an input form.
async function uploadDirectoryExample() {
try {
// Get the directory name from the list of files.
// Can also be named manually, i.e. if you build the files yourself
// instead of getting them from an input form.
const filename = getRootDirectory(files[0]);
// Use reduce to build the map of files indexed by filepaths
// (relative from the directory).
const directory = files.reduce((accumulator, file) => {
const path = getRelativeFilePath(file);
return { ...accumulator, [path]: file };
}, {});
const client = new SkynetClient();
const { skylink } = await client.uploadDirectory(directory, filename);
} catch (error) {
console.log(error);
}
}
```
### download(skylink)
```javascript
import { SkynetClient } from "skynet-js";
// Assume we have a skylink e.g. from a previous upload.
try {
const client = new SkynetClient();
client.download(skylink);
} catch (error) {
console.log(error);
}
```
Use the client to download `skylink` contents.
`skylink` (string) - 46 character skylink.
Returns nothing.
### open(skylink)
```javascript
import { SkynetClient } from "skynet-js";
```
Use the client to open `skylink` in a new browser tab. Browsers support opening natively only limited file extensions like .html or .jpg and will fallback to downloading the file.
`skylink` (string) - 46 character skylink.
Returns nothing.
### getDownloadUrl(skylink, [options])
```javascript
import { SkynetClient } from "skynet-js";
```
Use the client to generate direct `skylink` url.
`skylink` (string) - 46 character skylink.
`options.download` (boolean) - Option to include download directive in the url that will force a download when used. Defaults to `false`.
### parseSkylink(skylink)
```javascript
import { parseSkylink } from "skynet-js";
```
Use the `parseSkylink` to extract skylink from a string.
Currently supported string types are:
- direct skylink string, for example `"XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg"`
- `sia:` prefixed string, for example `"sia:XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg"`
- `sia://` prefixed string, for example `"sia://XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg"`
- skylink from url, for example `"https://siasky.net/XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg"`
`skylink` (string) - String containing 46 character skylink.
Returns extracted skylink string or throws error.
`skynet-js` provides functions that only make sense in the browser, and are covered in the special section [Browser JS Utilities](https://nebulouslabs.github.io/skynet-docs/?javascript--browser#browser-js-utilities).

@@ -1,10 +0,51 @@

import { defaultPortalUrl } from "./utils.js";
import axios from "axios";
import { addUrlQuery, defaultPortalUrl, makeUrl } from "./utils.js";
export class SkynetClient {
constructor(portalUrl = null) {
if (portalUrl === null) {
portalUrl = defaultPortalUrl();
}
/**
* The Skynet Client which can be used to access Skynet.
* @constructor
* @param {string} [portalUrl="https://siasky.net"] - The portal URL to use to access Skynet, if specified. To use the default portal while passing custom options, use "".
* @param {Object} [customOptions={}] - Configuration for the client.
* @param {string} [customOptions.method] - HTTP method to use.
* @param {string} [customOptions.APIKey] - Authentication password to use.
* @param {string} [customOptions.customUserAgent=""] - Custom user agent header to set.
* @param {Object} [customOptions.data=null] - Data to send in a POST.
* @param {string} [customOptions.endpointPath=""] - The relative URL path of the portal endpoint to contact.
* @param {string} [customOptions.extraPath=""] - Extra path element to append to the URL.
* @param {Function} [customOptions.onUploadProgress] - Optional callback to track progress.
* @param {Object} [customOptions.query={}] - Query parameters to include in the URl.
*/
constructor(portalUrl = defaultPortalUrl(), customOptions = {}) {
this.portalUrl = portalUrl;
this.customOptions = customOptions;
}
/**
* Creates and executes a request.
* @param {Object} config - Configuration for the request. See docs for constructor for the full list of options.
*/
executeRequest(config) {
let url = config.url;
if (!url) {
url = makeUrl(this.portalUrl, config.endpointPath, config.extraPath ?? "");
url = addUrlQuery(url, config.query);
}
return axios({
url: url,
method: config.method,
data: config.data,
headers: config.customUserAgent && { "User-Agent": config.customUserAgent },
auth: config.APIKey && { username: "", password: config.APIKey },
onUploadProgress:
config.onUploadProgress &&
function ({ loaded, total }) {
const progress = loaded / total;
config.onUploadProgress(progress, { loaded, total });
},
});
}
}
/* eslint-disable no-unused-vars */
import axios from "axios";
import { SkynetClient } from "./client.js";
import { makeUrlWithSkylink, defaultOptions } from "./utils.js";
import {
addUrlQuery,
defaultOptions,
makeUrl,
parseSkylink,
trimUriPrefix,
uriHandshakePrefix,
uriHandshakeResolverPrefix,
} from "./utils.js";

@@ -9,28 +19,110 @@ const defaultDownloadOptions = {

};
const defaultDownloadHnsOptions = {
...defaultOptions("/hns"),
};
const defaultResolveHnsOptions = {
...defaultOptions("/hnsres"),
};
SkynetClient.prototype.download = function (skylink, customOptions = {}) {
const opts = { ...defaultDownloadOptions, ...customOptions, download: true };
const url = this.getDownloadUrl(skylink, opts);
/**
* Initiates a download of the content of the skylink within the browser.
* @param {string} skylink - 46 character skylink.
* @param {Object} [customOptions={}] - Additional settings that can optionally be set.
* @param {string} [customOptions.endpointPath="/"] - The relative URL path of the portal endpoint to contact.
*/
SkynetClient.prototype.downloadFile = function (skylink, customOptions = {}) {
const opts = { ...defaultDownloadOptions, ...this.customOptions, ...customOptions, download: true };
const url = this.getSkylinkUrl(skylink, opts);
window.open(url, "_blank");
// Download the url.
window.location = url;
};
SkynetClient.prototype.getDownloadUrl = function (skylink, customOptions = {}) {
const opts = { ...defaultDownloadOptions, ...customOptions };
/**
* Initiates a download of the content of the skylink at the Handshake domain.
* @param {string} domain - Handshake domain.
* @param {Object} [customOptions={}] - Additional settings that can optionally be set.
* @param {string} [customOptions.endpointPath="/hns"] - The relative URL path of the portal endpoint to contact.
*/
SkynetClient.prototype.downloadFileHns = async function (domain, customOptions = {}) {
const opts = { ...defaultDownloadHnsOptions, ...this.customOptions, ...customOptions, download: true };
const url = this.getHnsUrl(domain, opts);
// Download the url.
window.location = url;
};
SkynetClient.prototype.getSkylinkUrl = function (skylink, customOptions = {}) {
const opts = { ...defaultDownloadOptions, ...this.customOptions, ...customOptions };
const query = opts.download ? { attachment: true } : {};
return makeUrlWithSkylink(this.portalUrl, opts.endpointPath, skylink, query);
const url = makeUrl(this.portalUrl, opts.endpointPath, parseSkylink(skylink));
return addUrlQuery(url, query);
};
SkynetClient.prototype.metadata = function (skylink, customOptions = {}) {
const opts = { ...defaultDownloadOptions, ...customOptions };
SkynetClient.prototype.getHnsUrl = function (domain, customOptions = {}) {
const opts = { ...defaultDownloadHnsOptions, ...this.customOptions, ...customOptions };
const query = opts.download ? { attachment: true } : {};
const url = makeUrl(this.portalUrl, opts.endpointPath, trimUriPrefix(domain, uriHandshakePrefix));
return addUrlQuery(url, query);
};
SkynetClient.prototype.getHnsresUrl = function (domain, customOptions = {}) {
const opts = { ...defaultResolveHnsOptions, ...this.customOptions, ...customOptions };
const query = opts.download ? { attachment: true } : {};
return makeUrl(this.portalUrl, opts.endpointPath, trimUriPrefix(domain, uriHandshakeResolverPrefix));
};
SkynetClient.prototype.getMetadata = async function (skylink, customOptions = {}) {
const opts = { ...defaultDownloadOptions, ...this.customOptions, ...customOptions };
throw new Error("Unimplemented");
};
SkynetClient.prototype.open = function (skylink, customOptions = {}) {
const opts = { ...defaultDownloadOptions, ...customOptions };
const url = makeUrlWithSkylink(this.portalUrl, opts.endpointPath, skylink);
/**
* Opens the content of the skylink within the browser.
* @param {string} skylink - 46 character skylink.
* @param {Object} [customOptions={}] - Additional settings that can optionally be set.
* @param {string} [customOptions.endpointPath="/"] - The relative URL path of the portal endpoint to contact.
*/
SkynetClient.prototype.openFile = function (skylink, customOptions = {}) {
const opts = { ...defaultDownloadOptions, ...this.customOptions, ...customOptions };
const url = this.getSkylinkUrl(skylink, opts);
window.open(url, "_blank");
};
/**
* Opens the content of the skylink from the given Handshake domain within the browser.
* @param {string} domain - Handshake domain.
* @param {Object} [customOptions={}] - Additional settings that can optionally be set.
* @param {string} [customOptions.endpointPath="/hns"] - The relative URL path of the portal endpoint to contact.
*/
SkynetClient.prototype.openFileHns = async function (domain, customOptions = {}) {
const opts = { ...defaultDownloadHnsOptions, ...this.customOptions, ...customOptions };
const url = this.getHnsUrl(domain, opts);
// Open the url in a new tab.
window.open(url, "_blank");
};
/**
* @param {string} domain - Handshake resolver domain.
* @param {Object} [customOptions={}] - Additional settings that can optionally be set.
* @param {string} [customOptions.endpointPath="/hnsres"] - The relative URL path of the portal endpoint to contact.
*/
SkynetClient.prototype.resolveHns = async function (domain, customOptions = {}) {
const opts = { ...defaultResolveHnsOptions, ...this.customOptions, ...customOptions };
const url = this.getHnsresUrl(domain, opts);
// Get the txt record from the hnsres domain on the portal.
const response = await this.executeRequest({
...opts,
method: "get",
url,
});
return response.data;
};

@@ -0,4 +1,12 @@

import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import { SkynetClient, defaultSkynetPortalUrl } from "./index";
const mock = new MockAdapter(axios);
const portalUrl = defaultSkynetPortalUrl;
const hnsLink = "foo";
const hnsUrl = `${portalUrl}/hns/${hnsLink}`;
const hnsresUrl = `${portalUrl}/hnsres/${hnsLink}`;
const client = new SkynetClient(portalUrl);

@@ -14,4 +22,6 @@ const skylink = "XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg";

];
const validHnsLinkVariations = [hnsLink, `hns:${hnsLink}`, `hns://${hnsLink}`];
const validHnsresLinkVariations = [hnsLink, `hnsres:${hnsLink}`, `hnsres://${hnsLink}`];
describe("download", () => {
describe("downloadFile", () => {
it("should call window.open with a download url with attachment set", () => {

@@ -23,6 +33,5 @@ const windowOpen = jest.spyOn(window, "open").mockImplementation();

client.download(input);
client.downloadFile(input);
expect(windowOpen).toHaveBeenCalledTimes(1);
expect(windowOpen).toHaveBeenCalledWith(`${portalUrl}/${skylink}?attachment=true`, "_blank");
expect(mock.history.get.length).toBe(0);
});

@@ -32,13 +41,35 @@ });

describe("getDownloadUrl", () => {
it("should return correctly formed download URL", () => {
describe("getHnsUrl", () => {
it("should return correctly formed hns URL", () => {
validHnsLinkVariations.forEach((input) => {
expect(client.getHnsUrl(input)).toEqual(hnsUrl);
});
});
it("should return correctly formed hns URL with forced download", () => {
const url = client.getHnsUrl(hnsLink, { download: true });
expect(url).toEqual(`${hnsUrl}?attachment=true`);
});
});
describe("getHnsresUrl", () => {
it("should return correctly formed hnsres URL", () => {
validHnsresLinkVariations.forEach((input) => {
expect(client.getHnsresUrl(input)).toEqual(hnsresUrl);
});
});
});
describe("getSkylinkUrl", () => {
it("should return correctly formed skylink URL", () => {
validSkylinkVariations.forEach((input) => {
expect(client.getDownloadUrl(input)).toEqual(`${portalUrl}/${skylink}`);
expect(client.getSkylinkUrl(input)).toEqual(`${portalUrl}/${skylink}`);
});
});
it("should return correctly formed url with forced download", () => {
const url = client.getDownloadUrl(skylink, { download: true });
it("should return correctly formed URL with forced download", () => {
const url = client.getSkylinkUrl(skylink, { download: true, endpointPath: "skynet/skylink" });
expect(url).toEqual(`${portalUrl}/${skylink}?attachment=true`);
expect(url).toEqual(`${portalUrl}/skynet/skylink/${skylink}?attachment=true`);
});

@@ -48,3 +79,3 @@ });

describe("open", () => {
it("should call window.open with a download url", () => {
it("should call window.openFile", () => {
const windowOpen = jest.spyOn(window, "open").mockImplementation();

@@ -55,3 +86,3 @@

client.open(input);
client.openFile(input);

@@ -63,1 +94,53 @@ expect(windowOpen).toHaveBeenCalledTimes(1);

});
describe("downloadFileHns", () => {
it("should set domain with the portal and hns link and then call window.openFile with attachment set", async () => {
const windowOpen = jest.spyOn(window, "open").mockImplementation();
for (const input of validHnsLinkVariations) {
mock.resetHistory();
windowOpen.mockReset();
await client.downloadFileHns(input);
expect(mock.history.get.length).toBe(0);
}
});
});
describe("openFileHns", () => {
const hnsUrl = `${portalUrl}/hns/${hnsLink}`;
it("should set domain with the portal and hns link and then call window.openFile", async () => {
const windowOpen = jest.spyOn(window, "open").mockImplementation();
for (const input of validHnsLinkVariations) {
mock.resetHistory();
windowOpen.mockReset();
await client.openFileHns(input);
expect(mock.history.get.length).toBe(0);
expect(windowOpen).toHaveBeenCalledTimes(1);
expect(windowOpen).toHaveBeenCalledWith(hnsUrl, "_blank");
}
});
});
describe("resolveHns", () => {
beforeEach(() => {
mock.onGet(hnsresUrl).reply(200, { skylink: 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);
}
});
});
/* eslint-disable no-unused-vars */
import { defaultOptions, SkynetClient } from "./utils.js";
import { SkynetClient } from "./client.js";
import { defaultOptions } from "./utils.js";

@@ -5,0 +6,0 @@ const defaultAddSkykeyOptions = {

@@ -5,2 +5,3 @@ export { SkynetClient } from "./client.js";

export {} from "./download.js";
export {} from "./encryption.js";
export {} from "./upload.js";

@@ -14,2 +15,5 @@

parseSkylink,
uriHandshakePrefix,
uriHandshakeResolverPrefix,
uriSkynetPrefix,
} from "./utils.js";

@@ -8,10 +8,25 @@ import { SkynetClient } from "./index";

// Download
expect(client).toHaveProperty("download");
expect(client).toHaveProperty("getDownloadUrl");
expect(client).toHaveProperty("open");
expect(client).toHaveProperty("downloadFile");
expect(client).toHaveProperty("downloadFileHns");
expect(client).toHaveProperty("getHnsUrl");
expect(client).toHaveProperty("getHnsresUrl");
expect(client).toHaveProperty("getSkylinkUrl");
expect(client).toHaveProperty("getMetadata");
expect(client).toHaveProperty("openFile");
expect(client).toHaveProperty("openFileHns");
expect(client).toHaveProperty("resolveHns");
// Encryption
expect(client).toHaveProperty("addSkykey");
expect(client).toHaveProperty("createSkykey");
expect(client).toHaveProperty("getSkykeyById");
expect(client).toHaveProperty("getSkykeyByName");
expect(client).toHaveProperty("getSkykeys");
// Upload
expect(client).toHaveProperty("upload");
expect(client).toHaveProperty("uploadFile");
expect(client).toHaveProperty("uploadFileRequest");
expect(client).toHaveProperty("uploadDirectory");
expect(client).toHaveProperty("uploadDirectoryRequest");
});
});

@@ -1,5 +0,3 @@

import axios from "axios";
import { SkynetClient } from "./client.js";
import { defaultOptions, makeUrl } from "./utils.js";
import { defaultOptions, uriSkynetPrefix } from "./utils.js";

@@ -10,26 +8,24 @@ const defaultUploadOptions = {

portalDirectoryFileFieldname: "files[]",
// TODO:
// customFilename: "",
customFilename: "",
};
SkynetClient.prototype.upload = async function (file, customOptions = {}) {
const opts = { ...defaultUploadOptions, ...customOptions };
SkynetClient.prototype.uploadFile = async function (file, customOptions = {}) {
const response = await this.uploadFileRequest(file, customOptions);
return `${uriSkynetPrefix}${response.skylink}`;
};
SkynetClient.prototype.uploadFileRequest = async function (file, customOptions = {}) {
const opts = { ...defaultUploadOptions, ...this.customOptions, ...customOptions };
const formData = new FormData();
formData.append(opts.portalFileFieldname, ensureFileObjectConsistency(file));
file = ensureFileObjectConsistency(file);
const filename = opts.customFilename ? opts.customFilename : "";
formData.append(opts.portalFileFieldname, file, filename);
const url = makeUrl(this.portalUrl, opts.endpointPath);
const { data } = await this.executeRequest({
...opts,
method: "post",
data: formData,
});
const { data } = await axios.post(
url,
formData,
opts.onUploadProgress && {
onUploadProgress: ({ loaded, total }) => {
const progress = loaded / total;
opts.onUploadProgress(progress, { loaded, total });
},
}
);
return data;

@@ -43,27 +39,33 @@ };

* @param {Object} [customOptions={}] - Additional settings that can optionally be set.
* @param {string} [config.APIKey] - Authentication password to use.
* @param {string} [config.customUserAgent=""] - Custom user agent header to set.
* @param {string} [customOptions.endpointPath="/skynet/skyfile"] - The relative URL path of the portal endpoint to contact.
* @param {Function} [config.onUploadProgress] - Optional callback to track progress.
* @param {string} [customOptions.portalDirectoryfilefieldname="files[]"] - The fieldName for directory files on the portal.
* @returns {Object} data - The returned data.
* @returns {string} data.skylink - The returned skylink.
* @returns {string} data.merkleroot - The hash that is encoded into the skylink.
* @returns {number} data.bitfield - The bitfield that gets encoded into the skylink.
*/
SkynetClient.prototype.uploadDirectory = async function (directory, filename, customOptions = {}) {
const opts = { ...defaultUploadOptions, ...customOptions };
const response = await this.uploadDirectoryRequest(directory, filename, customOptions);
return `${uriSkynetPrefix}${response.skylink}`;
};
SkynetClient.prototype.uploadDirectoryRequest = async function (directory, filename, customOptions = {}) {
const opts = { ...defaultUploadOptions, ...this.customOptions, ...customOptions };
const formData = new FormData();
Object.entries(directory).forEach(([path, file]) => {
formData.append(opts.portalDirectoryFileFieldname, ensureFileObjectConsistency(file), path);
file = ensureFileObjectConsistency(file);
formData.append(opts.portalDirectoryFileFieldname, file, path);
});
const url = makeUrl(this.portalUrl, opts.endpointPath, { filename });
const { data } = await this.executeRequest({
...opts,
method: "post",
data: formData,
query: { filename },
});
const { data } = await axios.post(
url,
formData,
opts.onUploadProgress && {
onUploadProgress: ({ loaded, total }) => {
const progress = loaded / total;
opts.onUploadProgress(progress, { loaded, total });
},
}
);
return data;

@@ -70,0 +72,0 @@ };

import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import { SkynetClient, defaultSkynetPortalUrl } from "./index";
import { SkynetClient, defaultSkynetPortalUrl, uriSkynetPrefix } from "./index";
import { compareFormData } from "./test_utils.js";
jest.mock("axios");
const mock = new MockAdapter(axios);

@@ -10,4 +12,6 @@ const portalUrl = defaultSkynetPortalUrl;

const skylink = "XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg";
const sialink = `${uriSkynetPrefix}${skylink}`;
describe("uploadFile", () => {
const url = `${portalUrl}/skynet/skyfile`;
const filename = "bar.txt";

@@ -19,82 +23,131 @@ const file = new File(["foo"], filename, {

beforeEach(() => {
axios.post.mockResolvedValue({ data: { skylink } });
mock.onPost(url).reply(200, { skylink: skylink });
mock.resetHistory();
});
it("should send post request with FormData", () => {
client.upload(file, {});
it("should send formdata with file", async () => {
const data = await client.uploadFile(file);
expect(axios.post).toHaveBeenCalledWith(
`${portalUrl}/skynet/skyfile`,
expect.any(FormData), // TODO: Inspect data contents.
undefined
);
expect(mock.history.post.length).toBe(1);
const request = mock.history.post[0];
await compareFormData(request.data, [["file", "foo", filename]]);
expect(data).toEqual(sialink);
});
it("should send register onUploadProgress callback if defined", () => {
it("should send register onUploadProgress callback if defined", async () => {
const newPortal = "https://my-portal.net";
const url = `${newPortal}/skynet/skyfile`;
const client = new SkynetClient(newPortal);
client.upload(file, { onUploadProgress: jest.fn() });
expect(axios.post).toHaveBeenCalledWith(
`${newPortal}/skynet/skyfile`,
expect.any(FormData), // TODO: Inspect data contents.
{
onUploadProgress: expect.any(Function),
}
);
// Use replyOnce to catch a single request with the new URL.
mock.onPost(url).replyOnce(200, { skylink: skylink });
const data = await client.uploadFile(file, { onUploadProgress: jest.fn() });
expect(mock.history.post.length).toBe(1);
const request = mock.history.post[0];
expect(request.onUploadProgress).toEqual(expect.any(Function));
await compareFormData(request.data, [["file", "foo", filename]]);
expect(data).toEqual(sialink);
});
it("should send base-64 authentication password if provided", () => {
client.upload(file, { APIKey: "foobar" });
it("should use custom filename if provided", async () => {
const data = await client.uploadFile(file, { customFilename: "testname" });
expect(axios.post).toHaveBeenCalledWith(
`${portalUrl}/skynet/skyfile`,
expect.any(FormData), // TODO: Inspect data contents.
undefined
);
expect(mock.history.post.length).toBe(1);
const request = mock.history.post[0];
await compareFormData(request.data, [["file", "foo", "testname"]]);
expect(data).toEqual(sialink);
});
it("should return skylink on success", async () => {
const data = await client.upload(file);
it("should send base-64 authentication password if provided", async () => {
const data = await client.uploadFile(file, { APIKey: "foobar" });
expect(data).toEqual({ skylink });
expect(mock.history.post.length).toBe(1);
const request = mock.history.post[0];
expect(request.auth).toEqual({ username: "", password: "foobar" });
await compareFormData(request.data, [["file", "foo", filename]]);
expect(data).toEqual(sialink);
});
it("should send custom user agent if defined", async () => {
const client = new SkynetClient(portalUrl, { customUserAgent: "Sia-Agent" });
const data = await client.uploadFile(file);
expect(mock.history.post.length).toBe(1);
const request = mock.history.post[0];
expect(request.headers["User-Agent"]).toEqual("Sia-Agent");
// Check that other headers weren't altered.
expect(request.headers["Content-Type"]).toEqual("application/x-www-form-urlencoded");
await compareFormData(request.data, [["file", "foo", filename]]);
expect(data).toEqual(sialink);
});
it("Should use user agent set in options to function", async () => {
const client = new SkynetClient(portalUrl, { customUserAgent: "Sia-Agent" });
const data = await client.uploadFile(file, { customUserAgent: "Sia-Agent-2" });
expect(mock.history.post.length).toBe(1);
const request = mock.history.post[0];
expect(request.headers["User-Agent"]).toEqual("Sia-Agent-2");
// Check that other headers weren't altered.
expect(request.headers["Content-Type"]).toEqual("application/x-www-form-urlencoded");
await compareFormData(request.data, [["file", "foo", filename]]);
expect(data).toEqual(sialink);
});
});
describe("uploadDirectory", () => {
const blob = new Blob([], { type: "image/jpeg" });
const filename = "i-am-root";
const directory = {
"i-am-not/file1.jpeg": new File([blob], "i-am-not/file1.jpeg"),
"i-am-not/file2.jpeg": new File([blob], "i-am-not/file2.jpeg"),
"i-am-not/me-neither/file3.jpeg": new File([blob], "i-am-not/me-neither/file3.jpeg"),
"i-am-not/file1.jpeg": new File(["foo1"], "i-am-not/file1.jpeg"),
"i-am-not/file2.jpeg": new File(["foo2"], "i-am-not/file2.jpeg"),
"i-am-not/me-neither/file3.jpeg": new File(["foo3"], "i-am-not/me-neither/file3.jpeg"),
};
const url = `${portalUrl}/skynet/skyfile?filename=${filename}`;
beforeEach(() => {
axios.post.mockResolvedValue({ data: { skylink } });
mock.onPost(url).reply(200, { skylink: skylink });
mock.resetHistory();
});
it("should send post request with FormData", () => {
client.uploadDirectory(directory, filename);
it("should send formdata with files", async () => {
const data = await client.uploadDirectory(directory, filename);
expect(axios.post).toHaveBeenCalledWith(
`${portalUrl}/skynet/skyfile?filename=${filename}`,
expect.any(FormData), // TODO: Inspect data contents.
undefined
);
});
expect(mock.history.post.length).toBe(1);
const request = mock.history.post[0];
it("should send register onUploadProgress callback if defined", () => {
client.uploadDirectory(directory, filename, { onUploadProgress: jest.fn() });
await compareFormData(request.data, [
["files[]", "foo1", "i-am-not/file1.jpeg"],
["files[]", "foo2", "i-am-not/file2.jpeg"],
["files[]", "foo3", "i-am-not/me-neither/file3.jpeg"],
]);
expect(axios.post).toHaveBeenCalledWith(`${portalUrl}/skynet/skyfile?filename=${filename}`, expect.any(FormData), {
onUploadProgress: expect.any(Function),
});
expect(data).toEqual(sialink);
});
it("should return single skylink on success", async () => {
const data = await client.uploadDirectory(directory, filename);
it("should send register onUploadProgress callback if defined", async () => {
const data = await client.uploadDirectory(directory, filename, { onUploadProgress: jest.fn() });
expect(data).toEqual({ skylink });
expect(mock.history.post.length).toBe(1);
const request = mock.history.post[0];
expect(request.onUploadProgress).toEqual(expect.any(Function));
expect(data).toEqual(sialink);
});
});
// import axios from "axios";
import path from "path-browserify";
import parse from "url-parse";
import urljoin from "url-join";
export const defaultSkynetPortalUrl = "https://siasky.net";
export function defaultPortalUrl() {
var url = new URL(window.location.href);
return url.href.substring(0, url.href.indexOf(url.pathname));
export const uriHandshakePrefix = "hns:";
export const uriHandshakeResolverPrefix = "hnsres:";
export const uriSkynetPrefix = "sia:";
export function addUrlQuery(url, query) {
const parsed = parse(url);
parsed.set("query", query);
return parsed.toString();
}

@@ -15,27 +21,15 @@

endpointPath: endpointPath,
// TODO:
// APIKey: "",
// customUserAgent: "",
APIKey: "",
customUserAgent: "",
};
}
// TODO: Use this to simplify creating requests. Needs to be tested.
// export function executeRequest(portalUrl, method, opts, query, data = {}) {
// const url = makeUrl(portalUrl, opts.endpointPath, query);
// TODO: This will be smarter. See
// https://github.com/NebulousLabs/skynet-docs/issues/21.
export function defaultPortalUrl() {
var url = new URL(window.location.href);
return url.href.substring(0, url.href.indexOf(url.pathname));
}
// return axios({
// method: method,
// url: url,
// data: data,
// auth: opts.APIKey && {username: "", password: opts.APIKey },
// onUploadProgress: opts.onUploadProgress && {
// onUploadProgress: ({ loaded, total }) => {
// const progress = loaded / total;
// opts.onUploadProgress(progress, { loaded, total });
// },
// }
// });
// }
function getFilePath(file) {

@@ -60,23 +54,19 @@ return file.webkitRelativePath || file.path || file.name;

export function makeUrl(portalUrl, pathname, query = {}) {
const parsed = parse(portalUrl);
parsed.set("pathname", pathname);
parsed.set("query", query);
return parsed.toString();
/**
* Properly joins paths together to create a URL. Takes a variable number of
* arguments.
* @returns {string} url - The URL.
*/
export function makeUrl(...args) {
return args.reduce(function (acc, cur) {
return urljoin(acc, cur);
});
}
export function makeUrlWithSkylink(portalUrl, endpointPath, skylink, query = {}) {
const parsedSkylink = parseSkylink(skylink);
return makeUrl(portalUrl, path.posix.join(endpointPath, parsedSkylink), query);
}
const SKYLINK_MATCHER = "([a-zA-Z0-9_-]{46})";
const SKYLINK_DIRECT_REGEX = new RegExp(`^${SKYLINK_MATCHER}$`);
const SKYLINK_SIA_PREFIXED_REGEX = new RegExp(`^sia:(?://)?${SKYLINK_MATCHER}$`);
const SKYLINK_PATHNAME_REGEX = new RegExp(`^/${SKYLINK_MATCHER}`);
const SKYLINK_REGEXP_MATCH_POSITION = 1;
export function parseSkylink(skylink = "") {
export function parseSkylink(skylink) {
if (typeof skylink !== "string") throw new Error(`Skylink has to be a string, ${typeof skylink} provided`);

@@ -91,4 +81,3 @@

// example: sia://XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg
const matchSiaPrefixed = skylink.match(SKYLINK_SIA_PREFIXED_REGEX);
if (matchSiaPrefixed) return matchSiaPrefixed[SKYLINK_REGEXP_MATCH_POSITION];
skylink = trimUriPrefix(skylink, uriSkynetPrefix);

@@ -103,1 +92,14 @@ // check for skylink passed in an url and extract it

}
export function trimUriPrefix(str, prefix) {
const longPrefix = `${prefix}//`;
if (str.startsWith(longPrefix)) {
// longPrefix is exactly at the beginning
return str.slice(longPrefix.length);
}
if (str.startsWith(prefix)) {
// else prefix is exactly at the beginning
return str.slice(prefix.length);
}
return str;
}

@@ -1,30 +0,62 @@

import { makeUrl, makeUrlWithSkylink, parseSkylink, defaultSkynetPortalUrl } from "./utils";
import {
addUrlQuery,
defaultSkynetPortalUrl,
makeUrl,
parseSkylink,
trimUriPrefix,
uriHandshakePrefix,
uriHandshakeResolverPrefix,
} from "./utils";
const portalUrl = defaultSkynetPortalUrl;
const skylink = "XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg";
const validSkylinkVariations = [
skylink,
`sia:${skylink}`,
`sia://${skylink}`,
`${portalUrl}/${skylink}`,
`${portalUrl}/${skylink}/foo/bar`,
`${portalUrl}/${skylink}?foo=bar`,
];
const hnsLink = "doesn";
const hnsresLink = "doesn";
describe("addUrlQuery", () => {
it("should return correctly formed URLs with query parameters", () => {
expect(addUrlQuery(portalUrl, { filename: "test" })).toEqual(`${portalUrl}?filename=test`);
expect(addUrlQuery(`${portalUrl}/path/`, { download: true })).toEqual(`${portalUrl}/path/?download=true`);
expect(addUrlQuery(`${portalUrl}/skynet/`, { foo: 1, bar: 2 })).toEqual(`${portalUrl}/skynet/?foo=1&bar=2`);
expect(addUrlQuery(`${portalUrl}/`, { attachment: true })).toEqual(`${portalUrl}/?attachment=true`);
});
});
describe("makeUrl", () => {
it("should return correctly formed URLs", () => {
expect(makeUrl(portalUrl, "/")).toEqual(`${portalUrl}/`);
expect(makeUrl(portalUrl, "/", { attachment: true })).toEqual(`${portalUrl}/?attachment=true`);
expect(makeUrl(portalUrl, "/skynet")).toEqual(`${portalUrl}/skynet`);
expect(makeUrl(portalUrl, "/skynet/")).toEqual(`${portalUrl}/skynet/`);
expect(makeUrl(portalUrl, "/skynet/", { foo: 1, bar: 2 })).toEqual(`${portalUrl}/skynet/?foo=1&bar=2`);
expect(makeUrlWithSkylink(portalUrl, "/", skylink)).toEqual(`${portalUrl}/${skylink}`);
expect(makeUrlWithSkylink(portalUrl, "/skynet", skylink)).toEqual(`${portalUrl}/skynet/${skylink}`);
expect(makeUrlWithSkylink(portalUrl, "/skynet/", skylink)).toEqual(`${portalUrl}/skynet/${skylink}`);
expect(makeUrl(portalUrl, "/", skylink)).toEqual(`${portalUrl}/${skylink}`);
expect(makeUrl(portalUrl, "/skynet", skylink)).toEqual(`${portalUrl}/skynet/${skylink}`);
expect(makeUrl(portalUrl, "//skynet/", skylink)).toEqual(`${portalUrl}/skynet/${skylink}`);
});
});
describe("trimUriPrefix", () => {
it("should correctly parse hns prefixed link", () => {
const validHnsLinkVariations = [hnsLink, `hns:${hnsLink}`, `hns://${hnsLink}`];
const validHnsresLinkVariations = [hnsresLink, `hnsres:${hnsresLink}`, `hnsres://${hnsresLink}`];
validHnsLinkVariations.forEach((input) => {
expect(trimUriPrefix(input, uriHandshakePrefix)).toEqual(hnsLink);
});
validHnsresLinkVariations.forEach((input) => {
expect(trimUriPrefix(input, uriHandshakeResolverPrefix)).toEqual(hnsresLink);
});
});
});
describe("parseSkylink", () => {
it("should correctly parse skylink out of different strings", () => {
const validSkylinkVariations = [
skylink,
`sia:${skylink}`,
`sia://${skylink}`,
`${portalUrl}/${skylink}`,
`${portalUrl}/${skylink}/foo/bar`,
`${portalUrl}/${skylink}?foo=bar`,
];
validSkylinkVariations.forEach((input) => {

@@ -36,3 +68,3 @@ expect(parseSkylink(input)).toEqual(skylink);

it("should throw on invalid skylink", () => {
expect(() => parseSkylink()).toThrowError("Could not extract skylink from ''");
expect(() => parseSkylink()).toThrowError("Skylink has to be a string, undefined provided");
expect(() => parseSkylink(123)).toThrowError("Skylink has to be a string, number provided");

@@ -39,0 +71,0 @@ expect(() => parseSkylink("123")).toThrowError("Could not extract skylink from '123'");

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