Comparing version 3.0.2 to 4.0.0-beta
@@ -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); |
@@ -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" | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
26
0
241712
15
76
4966
1
1
+ Addedpost-me@^0.4.5
+ Addedskynet-mysky-utils@^0.1.2
+ Addedpost-me@0.4.5(transitive)
+ Addedskynet-mysky-utils@0.1.2(transitive)
+ Addedurl-parse@1.4.7(transitive)
- Removedurl-parse@1.5.10(transitive)
Updatedurl-parse@1.4.7