Beta 7 with some repo names changed.

This commit is contained in:
Luis Fernando Molina committed 2020-11-15 13:05:19 +01:00
1 parent 0f7b40cca7
commit 963c19493a
3408 files changed
+462217 -15516

No files matched your search

+30
View File
@@ -0,0 +1,30 @@
const unavailable = () => { throw "This swarm.js function isn't available on the browser."; }
const fs = {readFile: unavailable};
const files = {download: unavailable, safeDownloadArchived: unavailable, directoryTree: unavailable};
const os = {platform: unavailable, arch: unavailable};
const path = {join: unavailable, slice: unavailable};
const child_process = {spawn: unavailable};
const mimetype = {lookup: unavailable};
const defaultArchives = {};
const downloadUrl = null;
const request = require("xhr-request");
const bytes = require("eth-lib/lib/bytes");
const hash = require("./swarm-hash.js");
const pick = require("./pick.js");
const swarm = require("./swarm");
module.exports = swarm({
fs,
files,
os,
path,
child_process,
defaultArchives,
mimetype,
request,
downloadUrl,
bytes,
hash,
pick
});
+42
View File
@@ -0,0 +1,42 @@
const fs = require("fs-extra");
const files = require("./files.js");
const os = require("os");
const path = require("path");
const child_process = require("child_process");
const mimetype = require('mime-types');
const defaultArchives = require("./../archives/archives.json");
const requester = require("xhr-request");
const downloadUrl = "http://ethereum-mist.s3.amazonaws.com/swarm/";
const bytes = require("eth-lib/lib/bytes");
const hash = require("./swarm-hash.js");
const pick = require("./pick.js");
const swarm = require("./swarm");
// Fixes issue that causes xhr-request-promise on Node.js to only accept Buffer
const request = (url, params, callback) => {
let newParams = {};
for (let key in params) {
newParams[key] = params[key];
}
if (typeof newParams.body !== "undefined") {
newParams.body = newParams.body instanceof Buffer
? newParams.body
: new Buffer(newParams.body);
}
return requester(url, newParams, callback);
};
module.exports = swarm({
fs,
files,
os,
path,
child_process,
defaultArchives,
mimetype,
request,
downloadUrl,
bytes,
hash,
pick
});
+193
View File
@@ -0,0 +1,193 @@
// This module implements some file download utils. Its most important export
// is `safeDownloadTargzFile`, which, given a file, its md5, a tar.gz url, its
// md5 and a path, returns a Promise that will only resolve once the exact file
// you expect is available on that path.
const Q = require("bluebird");
const assert = require("assert");
const crypto = require("crypto");
const fs = require("fs-extra");
const got = require("got");
const mkdirp = require("mkdirp-promise");
const path = require("path");
const tar = require("tar");
// String -> String ~> Promise String
// Downloads a file from an url to a path.
// Returns a promise containing the path.
const download = url => filePath => {
const promise = Q.resolve(mkdirp(path.dirname(filePath))).then(() =>
new Q((resolve, reject) => {
const writeStream = fs.createWriteStream(filePath);
const downloadStream = got.stream(url);
downloadStream.on("end", () => resolve(filePath));
downloadStream.on("data", chunk => promise.onDataCallback(chunk));
downloadStream.on("error", reject);
downloadStream.pipe(writeStream);
}));
promise.onDataCallback = () => {};
promise.onData = callback => {
promise.onDataCallback = callback || (() => {});
return promise;
}
return promise;
};
// String -> String ~> Promise String
// Hashes a file using the given algorithm (ex: "md5").
// Returns a promise containing the hashed string.
const hash = algorithm => path =>
new Q((resolve, reject) => {
const readStream = fs.ReadStream(path);
const hash = crypto.createHash(algorithm);
readStream.on("data", d => hash.update(d));
readStream.on("end", () => resolve(hash.digest("hex")));
readStream.on("error", reject);
});
// String -> String ~> Promise ()
// Asserts a file matches this md5 hash.
// Returns a promise containing its path.
const checksum = fileHash => path =>
hash("md5")(path)
.then(actualHash => actualHash === fileHash)
.then(assert)
.then(() => path);
// String ~> String ~> String ~> Promise String
// Downloads a file to a directory, check.
// Checks if the md5 hash matches.
// Returns a promise containing the path.
const downloadAndCheck = url => path => fileHash =>
download(url)(path)
.then(checksum(fileHash));
// String -> String ~> Promise String
// TODO: work for zip and other types
const extract = fromPath => toPath =>
tar.x(fromPath, toPath)
.then(() => toPath);
// String ~> Promise String
// Reads a file as an UTF8 string.
// Returns a promise containing that string.
const readUTF8 = path =>
fs.readFile (path, {encoding: "utf8"});
// String ~> Promise Bool
const isDirectory = path =>
fs.exists(path)
.then(assert)
.then(() => fs.lstat(path))
.then(stats => stats.isDirectory())
.catch(() => false);
// String -> Promise String
const directoryTree = dirPath => {
let paths = [];
const search = dirPath =>
isDirectory(dirPath).then(isDir => {
if (isDir) {
const searchOnDir = dir => search (path.join(dirPath, dir));
return Q.all(Q.map(fs.readdir(dirPath), searchOnDir));
} else {
paths.push(dirPath);
};
});
return Q.all(search (dirPath)).then(() => paths);
}
// Regex -> String ~> Promise (Array String)
const search = regex => dirPath =>
directoryTree(dirPath)
.then(tree => tree.filter(path => regex.test(path)));
// String -> String -> String -> String ~> Promise String
// Downloads a file inside a tar.gz and places it at `filePath`.
// Checks the md5 hash of the tar before extracting it.
// Checks the md5 hash of the file after extracting it.
// If all is OK, returns a promise containing the file path.
const safeDownloadArchived = url => archiveHash => fileHash => filePath => {
const fileDir = path.dirname(filePath);
const fileName = path.basename(filePath);
const archivePath = path.join(fileDir, ".swarm_downloads/files.tar.gz");
const archiveDir = path.dirname(archivePath);
const promise = Q.resolve(mkdirp(archiveDir))
.then(() => checksum (fileHash) (filePath))
.then(() => filePath)
.catch(() => fs.exists(archiveDir)
.then(exists => !exists ? fs.mkdir(archiveDir) : null)
.then(() => download (url)(archivePath).onData(promise.onDataCallback))
.then(() => hash("md5")(archivePath))
.then(() => archiveHash ? checksum(archiveHash)(archivePath) : null)
.then(() => extract (archivePath) (archiveDir))
.then(() => search (new RegExp(fileName+"$")) (archiveDir))
.then(fp => fs.rename (fp[0], filePath))
.then(() => fs.unlink (archivePath))
.then(() => fileHash ? checksum(fileHash)(filePath) : null)
.then(() => filePath));
promise.onDataCallback = () => {};
promise.onData = callback => {
promise.onDataCallback = callback || (() => {});
return promise;
};
return promise;
};
// String -> String ~> Promise String
// Like `safeDownloadArchivedFile`, but without the checksums.
const downloadArchived = url => path =>
safeDownloadArchived (url) (null) (null) (path);
// () => Promise Bool
// Tests the implementation by downloading a predetermined tar.gz
// from a mocked HTTP-server into a mocked filesystem. Does some
// redundancy tests such as checking the file constents and double
// checking its MD5 hash.
// Returns a promise containing a boolean, true if tests passed.
const test = () => {
const filePath = "/swarm/foo.txt";
const fileHash = "d3b07384d113edec49eaa6238ad5ff00";
const archiveUrl = "http://localhost:12534";
const archiveHash = "7fa45f946bb2a696bdd9972e0fbceac2";
const archiveData = new Buffer([
0x1f,0x8b,0x08,0x00,0xf1,0x34,0xaf,0x58,0x00,0x03,0xed,0xcf,0x3d,0x0e,0x83,0x30,
0x0c,0x86,0x61,0x66,0x4e,0xe1,0x13,0x54,0xce,0x0f,0xc9,0x79,0x58,0xb2,0x46,0x82,
0x14,0x71,0x7c,0xd2,0x06,0x31,0x52,0x75,0x40,0x08,0xe9,0x7d,0x96,0x4f,0x96,0x3d,
0x7c,0x4e,0x39,0xbf,0xca,0x5a,0xba,0x2b,0xa9,0x6a,0xf0,0x5e,0x3e,0x19,0xc3,0xf0,
0x4d,0xb5,0x6d,0xde,0x79,0x31,0x4e,0x07,0x17,0x9c,0xb5,0x31,0x8a,0x1a,0xab,0xc6,
0x77,0xa2,0x97,0xb6,0xda,0xbd,0xe7,0x32,0x4e,0xb5,0xca,0xf2,0xe3,0xae,0x9e,0xa5,
0x74,0xb2,0x6f,0x8f,0xc8,0x91,0x0f,0x91,0x72,0xee,0xef,0xee,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xdf,0x06,0xb3,0x2a,0xda,
0xed,0x00,0x28,0x00,0x00]);
const crypto = require("crypto");
const fsMock = require("mock-fs")({"/swarm":{}});
const httpMock = require("http")
.createServer((_,res) => res.end(archiveData))
.listen(12534);
return safeDownloadArchived (archiveUrl) (archiveHash) (fileHash) (filePath)
.then(checksum (fileHash))
.then(readUTF8)
.then(text => text === "foo\n")
.then(assert)
.then(() => safeDownloadArchived (archiveUrl) (archiveHash) (fileHash) (filePath))
.then(() => true)
.catch(false)
.finally(() => httpMock.close());
};
module.exports = {
download,
hash,
checksum,
downloadAndCheck,
extract,
readUTF8,
safeDownloadArchived,
directoryTree,
downloadArchived,
search,
test
};
+54
View File
@@ -0,0 +1,54 @@
const picker = type => () => new Promise((resolve, reject) => {
const fileLoader = e => {
const directory = {};
const totalFiles = e.target.files.length;
let loadedFiles = 0;
[].map.call(e.target.files, file => {
const reader = new FileReader();
reader.onload = e => {
const data = new Uint8Array(e.target.result);
if (type === "directory") {
const path = file.webkitRelativePath;
directory[path.slice(path.indexOf("/")+1)] = {
type: "text/plain",
data: data
};
if (++loadedFiles === totalFiles)
resolve(directory);
} else if (type === "file") {
const path = file.webkitRelativePath;
resolve({"type": mimetype.lookup(path), "data": data});
} else {
resolve(data);
}
};
reader.readAsArrayBuffer(file);
});
};
let fileInput;
if (type === "directory") {
fileInput = document.createElement("input");
fileInput.addEventListener("change", fileLoader);
fileInput.type = "file";
fileInput.webkitdirectory = true;
fileInput.mozdirectory = true;
fileInput.msdirectory = true;
fileInput.odirectory = true;
fileInput.directory = true;
} else {
fileInput = document.createElement("input");
fileInput.addEventListener("change", fileLoader);
fileInput.type = "file";
};
const mouseEvent = document.createEvent("MouseEvents");
mouseEvent.initEvent("click", true, false);
fileInput.dispatchEvent(mouseEvent);
});
module.exports = {
data: picker("data"),
file: picker("file"),
directory: picker("directory"),
}
+40
View File
@@ -0,0 +1,40 @@
// Thanks https://github.com/axic/swarmhash
const keccak = require("eth-lib/lib/hash").keccak256;
const Bytes = require("eth-lib/lib/bytes");
const swarmHashBlock = (length, data) => {
const lengthEncoded = Bytes.reverse(Bytes.pad(6, Bytes.fromNumber(length)));
const bytes = Bytes.flatten([lengthEncoded, "0x0000", data]);
return keccak(bytes).slice(2);
};
// (Bytes | Uint8Array | String) -> String
const swarmHash = data => {
if (typeof data === "string" && data.slice(0,2) !== "0x") {
data = Bytes.fromString(data);
} else if (typeof data !== "string" && data.length !== undefined) {
data = Bytes.fromUint8Array(data);
}
const length = Bytes.length(data);
if (length <= 4096) {
return swarmHashBlock(length, data);
}
let maxSize = 4096;
while ((maxSize * (4096 / 32)) < length) {
maxSize *= (4096 / 32);
}
let innerNodes = [];
for (let i = 0; i < length; i += maxSize) {
const size = (maxSize < (length - i)) ? maxSize : (length - i);
innerNodes.push(swarmHash(Bytes.slice(data, i, i + size)));
}
return swarmHashBlock(length, Bytes.flatten(innerNodes));
};
module.exports = swarmHash;
+505
View File
@@ -0,0 +1,505 @@
// TODO: this is a temporary fix to hide those libraries from the browser. A
// slightly better long-term solution would be to split this file into two,
// separating the functions that are used on Node.js from the functions that
// are used only on the browser.
module.exports = ({
fs,
files,
os,
path,
child_process,
mimetype,
defaultArchives,
request,
downloadUrl,
bytes,
hash,
pick
}) => {
// ∀ a . String -> JSON -> Map String a -o Map String a
// Inserts a key/val pair in an object impurely.
const impureInsert = key => val => map =>
(map[key] = val, map);
// String -> JSON -> Map String JSON
// Merges an array of keys and an array of vals into an object.
const toMap = keys => vals => {
let map = {};
for (let i = 0, l = keys.length; i < l; ++i)
map[keys[i]] = vals[i];
return map;
};
// ∀ a . Map String a -> Map String a -> Map String a
// Merges two maps into one.
const merge = a => b => {
let map = {};
for (let key in a)
map[key] = a[key];
for (let key in b)
map[key] = b[key];
return map;
};
// ∀ a . [a] -> [a] -> Bool
const equals = a => b => {
if (a.length !== b.length) {
return false;
} else {
for (let i = 0, l = a.length; i < l; ++i) {
if (a[i] !== b[i]) return false;
}
}
return true;
}
// String -> String -> String
const rawUrl = swarmUrl => hash =>
`${swarmUrl}/bzz-raw:/${hash}`
// String -> String -> Promise Uint8Array
// Gets the raw contents of a Swarm hash address.
const downloadData = swarmUrl => hash =>
new Promise((resolve, reject) => {
request(rawUrl(swarmUrl)(hash), {responseType: "arraybuffer"}, (err, arrayBuffer, response) => {
if (err) {
return reject(err);
}
if (response.statusCode >= 400) {
return reject(new Error(`Error ${response.statusCode}.`));
}
return resolve(new Uint8Array(arrayBuffer));
})
});
// type Entry = {"type": String, "hash": String}
// type File = {"type": String, "data": Uint8Array}
// String -> String -> Promise (Map String Entry)
// Solves the manifest of a Swarm address recursively.
// Returns a map from full paths to entries.
const downloadEntries = swarmUrl => hash => {
const search = hash => path => routes => {
// Formats an entry to the Swarm.js type.
const format = entry => ({
type: entry.contentType,
hash: entry.hash});
// To download a single entry:
// if type is bzz-manifest, go deeper
// if not, add it to the routing table
const downloadEntry = entry => {
if (entry.path === undefined) {
return Promise.resolve();
} else {
return entry.contentType === "application/bzz-manifest+json"
? search (entry.hash) (path + entry.path) (routes)
: Promise.resolve (impureInsert (path + entry.path) (format(entry)) (routes));
}
}
// Downloads the initial manifest and then each entry.
return downloadData(swarmUrl)(hash)
.then(text => JSON.parse(toString(text)).entries)
.then(entries => Promise.all(entries.map(downloadEntry)))
.then(() => routes);
}
return search (hash) ("") ({});
}
// String -> String -> Promise (Map String String)
// Same as `downloadEntries`, but returns only hashes (no types).
const downloadRoutes = swarmUrl => hash =>
downloadEntries(swarmUrl)(hash)
.then(entries => toMap
(Object.keys(entries))
(Object.keys(entries).map(route => entries[route].hash)));
// String -> String -> Promise (Map String File)
// Gets the entire directory tree in a Swarm address.
// Returns a promise mapping paths to file contents.
const downloadDirectory = swarmUrl => hash =>
downloadEntries (swarmUrl) (hash)
.then(entries => {
const paths = Object.keys(entries);
const hashs = paths.map(path => entries[path].hash);
const types = paths.map(path => entries[path].type);
const datas = hashs.map(downloadData(swarmUrl));
const files = datas => datas.map((data, i) => ({type: types[i], data: data}));
return Promise.all(datas).then(datas => toMap(paths)(files(datas)));
});
// String -> String -> String -> Promise String
// Gets the raw contents of a Swarm hash address.
// Returns a promise with the downloaded file path.
const downloadDataToDisk = swarmUrl => hash => filePath =>
files.download (rawUrl(swarmUrl)(hash)) (filePath);
// String -> String -> String -> Promise (Map String String)
// Gets the entire directory tree in a Swarm address.
// Returns a promise mapping paths to file contents.
const downloadDirectoryToDisk = swarmUrl => hash => dirPath =>
downloadRoutes (swarmUrl) (hash)
.then(routingTable => {
let downloads = [];
for (let route in routingTable) {
if (route.length > 0) {
const filePath = path.join(dirPath, route);
downloads.push(downloadDataToDisk(swarmUrl)(routingTable[route])(filePath));
};
};
return Promise.all(downloads).then(() => dirPath);
});
// String -> Uint8Array -> Promise String
// Uploads raw data to Swarm.
// Returns a promise with the uploaded hash.
const uploadData = swarmUrl => data =>
new Promise((resolve, reject) => {
const params = {
body: typeof data === "string" ? fromString(data) : data,
method: "POST"
};
request(`${swarmUrl}/bzz-raw:/`, params, (err, data) => {
if (err) {
return reject(err);
}
return resolve(data);
});
});
// String -> String -> String -> File -> Promise String
// Uploads a file to the Swarm manifest at a given hash, under a specific
// route. Returns a promise containing the uploaded hash.
// FIXME: for some reasons Swarm-Gateways is sometimes returning
// error 404 (bad request), so we retry up to 3 times. Why?
const uploadToManifest = swarmUrl => hash => route => file => {
const attempt = n => {
const slashRoute = route[0] === "/" ? route : "/" + route;
const url = `${swarmUrl}/bzz:/${hash}${slashRoute}`;
const opt = {
method: "PUT",
headers: {"Content-Type": file.type},
body: file.data};
return new Promise((resolve, reject) => {
request(url, opt, (err, data) => {
if (err) {
return reject(err);
}
if (data.indexOf("error") !== -1) {
return reject(data);
}
return resolve(data);
});
}).catch(e => n > 0 && attempt(n-1));
};
return attempt(3);
};
// String -> {type: String, data: Uint8Array} -> Promise String
const uploadFile = swarmUrl => file =>
uploadDirectory(swarmUrl)({"": file});
// String -> String -> Promise String
const uploadFileFromDisk = swarmUrl => filePath =>
fs.readFile(filePath)
.then(data => uploadFile(swarmUrl)({type: mimetype.lookup(filePath), data: data}));
// String -> Map String File -> Promise String
// Uploads a directory to Swarm. The directory is
// represented as a map of routes and files.
// A default path is encoded by having a "" route.
const uploadDirectory = swarmUrl => directory =>
uploadData(swarmUrl)("{}")
.then(hash => {
const uploadRoute = route => hash => uploadToManifest(swarmUrl)(hash)(route)(directory[route]);
const uploadToHash = (hash, route) => hash.then(uploadRoute(route));
return Object.keys(directory).reduce(uploadToHash, Promise.resolve(hash));
});
// String -> Promise String
const uploadDataFromDisk = swarmUrl => filePath =>
fs.readFile(filePath)
.then(uploadData(swarmUrl));
// String -> Nullable String -> String -> Promise String
const uploadDirectoryFromDisk = swarmUrl => defaultPath => dirPath =>
files.directoryTree(dirPath)
.then(fullPaths => Promise.all(fullPaths.map(path => fs.readFile(path))).then(datas => {
const paths = fullPaths.map(path => path.slice(dirPath.length));
const types = fullPaths.map(path => mimetype.lookup(path) || "text/plain");
return toMap (paths) (datas.map((data, i) => ({type: types[i], data: data})));
}))
.then(directory => merge (defaultPath ? {"": directory[defaultPath]} : {}) (directory))
.then(uploadDirectory(swarmUrl));
// String -> UploadInfo -> Promise String
// Simplified multi-type upload which calls the correct
// one based on the type of the argument given.
const upload = swarmUrl => arg => {
// Upload raw data from browser
if (arg.pick === "data") {
return pick.data().then(uploadData(swarmUrl));
// Upload a file from browser
} else if (arg.pick === "file") {
return pick.file().then(uploadFile(swarmUrl));
// Upload a directory from browser
} else if (arg.pick === "directory") {
return pick.directory().then(uploadDirectory(swarmUrl));
// Upload directory/file from disk
} else if (arg.path) {
switch (arg.kind) {
case "data": return uploadDataFromDisk(swarmUrl)(arg.path);
case "file": return uploadFileFromDisk(swarmUrl)(arg.path);
case "directory": return uploadDirectoryFromDisk(swarmUrl)(arg.defaultFile)(arg.path);
};
// Upload UTF-8 string or raw data (buffer)
} else if (arg.length || typeof arg === "string") {
return uploadData(swarmUrl)(arg);
// Upload directory with JSON
} else if (arg instanceof Object) {
return uploadDirectory(swarmUrl)(arg);
}
return Promise.reject(new Error("Bad arguments"));
}
// String -> String -> Nullable String -> Promise (String | Uint8Array | Map String Uint8Array)
// Simplified multi-type download which calls the correct function based on
// the type of the argument given, and on whether the Swwarm address has a
// directory or a file.
const download = swarmUrl => hash => path =>
isDirectory(swarmUrl)(hash).then(isDir => {
if (isDir) {
return path
? downloadDirectoryToDisk(swarmUrl)(hash)(path)
: downloadDirectory(swarmUrl)(hash);
} else {
return path
? downloadDataToDisk(swarmUrl)(hash)(path)
: downloadData(swarmUrl)(hash);
}
});
// String -> Promise String
// Downloads the Swarm binaries into a path. Returns a promise that only
// resolves when the exact Swarm file is there, and verified to be correct.
// If it was already there to begin with, skips the download.
const downloadBinary = (path, archives) => {
const system = os.platform().replace("win32","windows") + "-" + (os.arch() === "x64" ? "amd64" : "386");
const archive = (archives || defaultArchives)[system];
const archiveUrl = downloadUrl + archive.archive + ".tar.gz";
const archiveMD5 = archive.archiveMD5;
const binaryMD5 = archive.binaryMD5;
return files.safeDownloadArchived(archiveUrl)(archiveMD5)(binaryMD5)(path);
};
// type SwarmSetup = {
// account : String,
// password : String,
// dataDir : String,
// binPath : String,
// ensApi : String,
// onDownloadProgress : Number ~> (),
// archives : [{
// archive: String,
// binaryMD5: String,
// archiveMD5: String
// }]
// }
// SwarmSetup ~> Promise Process
// Starts the Swarm process.
const startProcess = swarmSetup => new Promise((resolve, reject) => {
const {spawn} = child_process;
const hasString = str => buffer => ('' + buffer).indexOf(str) !== -1;
const {account, password, dataDir, ensApi, privateKey} = swarmSetup;
const STARTUP_TIMEOUT_SECS = 3;
const WAITING_PASSWORD = 0;
const STARTING = 1;
const LISTENING = 2;
const PASSWORD_PROMPT_HOOK = "Passphrase";
const LISTENING_HOOK = "Swarm http proxy started";
let state = WAITING_PASSWORD;
const swarmProcess = spawn(swarmSetup.binPath, [
'--bzzaccount', account || privateKey,
'--datadir', dataDir,
'--ens-api', ensApi]);
const handleProcessOutput = data => {
if (state === WAITING_PASSWORD && hasString (PASSWORD_PROMPT_HOOK) (data)) {
setTimeout(() => {
state = STARTING;
swarmProcess.stdin.write(password + '\n');
}, 500);
} else if (hasString (LISTENING_HOOK) (data)) {
state = LISTENING;
clearTimeout(timeout);
resolve(swarmProcess);
}
}
swarmProcess.stdout.on('data', handleProcessOutput);
swarmProcess.stderr.on('data', handleProcessOutput);
//swarmProcess.on('close', () => setTimeout(restart, 2000));
let restart = () => startProcess(swarmSetup).then(resolve).catch(reject);
let error = () => reject(new Error("Couldn't start swarm process."));
let timeout = setTimeout(error, 20000);
});
// Process ~> Promise ()
// Stops the Swarm process.
const stopProcess = process => new Promise((resolve, reject) => {
process.stderr.removeAllListeners('data');
process.stdout.removeAllListeners('data');
process.stdin.removeAllListeners('error');
process.removeAllListeners('error');
process.removeAllListeners('exit');
process.kill('SIGINT');
const killTimeout = setTimeout(
() => process.kill('SIGKILL'),
8000);
process.once('close', () => {
clearTimeout(killTimeout);
resolve();
});
});
// SwarmSetup -> (SwarmAPI -> Promise ()) -> Promise ()
// Receives a Swarm configuration object and a callback function. It then
// checks if a local Swarm node is running. If no local Swarm is found, it
// downloads the Swarm binaries to the dataDir (if not there), checksums,
// starts the Swarm process and calls the callback function with an API
// object using the local node. That callback must return a promise which
// will resolve when it is done using the API, so that this function can
// close the Swarm process properly. Returns a promise that resolves when the
// user is done with the API and the Swarm process is closed.
// TODO: check if Swarm process is already running (improve `isAvailable`)
const local = swarmSetup => useAPI =>
isAvailable("http://localhost:8500").then(isAvailable =>
isAvailable
? useAPI(at("http://localhost:8500")).then(() => {})
: downloadBinary(swarmSetup.binPath, swarmSetup.archives)
.onData(data => (swarmSetup.onProgress || (() => {}))(data.length))
.then(() => startProcess(swarmSetup))
.then(process => useAPI(at("http://localhost:8500")).then(() => process))
.then(stopProcess));
// String ~> Promise Bool
// Returns true if Swarm is available on `url`.
// Perfoms a test upload to determine that.
// TODO: improve this?
const isAvailable = swarmUrl => {
const testFile = "test";
const testHash = "c9a99c7d326dcc6316f32fe2625b311f6dc49a175e6877681ded93137d3569e7";
return uploadData(swarmUrl)(testFile)
.then(hash => hash === testHash)
.catch(() => false);
};
// String -> String ~> Promise Bool
// Returns a Promise which is true if that Swarm address is a directory.
// Determines that by checking that it (i) is a JSON, (ii) has a .entries.
// TODO: improve this?
const isDirectory = swarmUrl => hash =>
downloadData(swarmUrl)(hash)
.then(data => {
try {
return !!JSON.parse(toString(data)).entries;
} catch (e) {
return false;
}
});
// Uncurries a function; used to allow the f(x,y,z) style on exports.
const uncurry = f => (a,b,c,d,e) => {
var p;
// Hardcoded because efficiency (`arguments` is very slow).
if (typeof a !== "undefined") p = f(a);
if (typeof b !== "undefined") p = f(b);
if (typeof c !== "undefined") p = f(c);
if (typeof d !== "undefined") p = f(d);
if (typeof e !== "undefined") p = f(e);
return p;
};
// () -> Promise Bool
// Not sure how to mock Swarm to test it properly. Ideas?
const test = () => Promise.resolve(true);
// Uint8Array -> String
const toString = uint8Array =>
bytes.toString(bytes.fromUint8Array(uint8Array));
// String -> Uint8Array
const fromString = string =>
bytes.toUint8Array(bytes.fromString(string));
// String -> SwarmAPI
// Fixes the `swarmUrl`, returning an API where you don't have to pass it.
const at = swarmUrl => ({
download: (hash,path) => download(swarmUrl)(hash)(path),
downloadData: uncurry(downloadData(swarmUrl)),
downloadDataToDisk: uncurry(downloadDataToDisk(swarmUrl)),
downloadDirectory: uncurry(downloadDirectory(swarmUrl)),
downloadDirectoryToDisk: uncurry(downloadDirectoryToDisk(swarmUrl)),
downloadEntries: uncurry(downloadEntries(swarmUrl)),
downloadRoutes: uncurry(downloadRoutes(swarmUrl)),
isAvailable: () => isAvailable(swarmUrl),
upload: (arg) => upload(swarmUrl)(arg),
uploadData: uncurry(uploadData(swarmUrl)),
uploadFile: uncurry(uploadFile(swarmUrl)),
uploadFileFromDisk: uncurry(uploadFile(swarmUrl)),
uploadDataFromDisk: uncurry(uploadDataFromDisk(swarmUrl)),
uploadDirectory: uncurry(uploadDirectory(swarmUrl)),
uploadDirectoryFromDisk: uncurry(uploadDirectoryFromDisk(swarmUrl)),
uploadToManifest: uncurry(uploadToManifest(swarmUrl)),
pick: pick,
hash: hash,
fromString: fromString,
toString: toString
});
return {
at,
local,
download,
downloadBinary,
downloadData,
downloadDataToDisk,
downloadDirectory,
downloadDirectoryToDisk,
downloadEntries,
downloadRoutes,
isAvailable,
startProcess,
stopProcess,
upload,
uploadData,
uploadDataFromDisk,
uploadFile,
uploadFileFromDisk,
uploadDirectory,
uploadDirectoryFromDisk,
uploadToManifest,
pick,
hash,
fromString,
toString
};
};