@anythread/gsoc
Version:
Graffiti Several Owner Chunk implementation on Swarm network
278 lines (277 loc) • 11.6 kB
JavaScript
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.uploadSingleOwnerChunkData = uploadSingleOwnerChunkData;
exports.gsocSubscribe = gsocSubscribe;
exports.createPostageBatch = createPostageBatch;
exports.listPostageBatches = listPostageBatches;
exports.getPostageBatch = getPostageBatch;
exports.getNodeAddresses = getNodeAddresses;
const bmt_js_1 = require("@nugaon/bmt-js");
const isomorphic_ws_1 = __importDefault(require("isomorphic-ws"));
const soc_1 = require("./soc");
const utils_1 = require("./utils");
const axios_1 = __importStar(require("axios"));
/**
* Helper function to create and upload SOC.
*/
function uploadSingleOwnerChunkData(requestOptions, postageBatchId, signer, identifier, payload, options) {
return __awaiter(this, void 0, void 0, function* () {
const cac = (0, bmt_js_1.makeChunk)(payload);
const soc = yield (0, soc_1.makeSingleOwnerChunk)(cac, identifier, signer);
const owner = (0, utils_1.bytesToHex)(soc.owner());
const id = (0, utils_1.bytesToHex)(identifier);
const signature = (0, utils_1.bytesToHex)(soc.signature());
const data = (0, utils_1.serializeBytes)(soc.span(), soc.payload);
yield uploadSoc(requestOptions, owner, id, signature, data, postageBatchId, options);
return soc;
});
}
/**
* Subscribe to messages for given topic with GSOC
*
* @param address SOC address under which payloads
* @param handler Message handler interface
*
* @returns close() function on websocket connection
*/
function gsocSubscribe(baseUrl, address, handler) {
assertSubscriptionHandler(handler);
if (typeof address !== 'string' || address.length !== 64) {
throw new TypeError('soc address has to be an string and 32 bytes!');
}
const ws = webSocket(baseUrl, `gsoc/subscribe/${address}`);
let closed = false;
const close = () => {
if (closed === false) {
closed = true;
// although the WebSocket API offers a `close` function, it seems that
// with the library that we are using (isomorphic-ws) it doesn't close
// the websocket properly, whereas `terminate` does
if (ws.terminate)
ws.terminate();
else
ws.close(); // standard Websocket in browser does not have terminate function
}
};
ws.onmessage = (ev) => __awaiter(this, void 0, void 0, function* () {
const data = prepareWebsocketData(ev.data);
// ignore empty messages
if (data.length > 0) {
handler.onMessage((0, utils_1.wrapBytesWithHelpers)(data));
}
});
ws.onerror = ev => {
// ignore errors after subscription was cancelled
if (!closed) {
handler.onError(new BeeError(ev.message));
}
};
return close;
}
/**
* Upload single owner chunk (SOC) to a Bee node
*
* @param requestOptions BeeRequestOptions
* @param owner Owner's ethereum address in hex
* @param identifier Arbitrary identifier in hex
* @param signature Signature in hex
* @param data Content addressed chunk data to be uploaded
* @param postageBatchId Postage BatchId that will be assigned to uploaded data
* @param options Additional options like tag, encryption, pinning
*/
function uploadSoc(requestOptions, owner, identifier, signature, data, postage, options) {
return __awaiter(this, void 0, void 0, function* () {
const response = yield http(Object.assign(Object.assign({}, requestOptions), { method: 'post', url: `soc/${owner}/${identifier}`, data, headers: Object.assign({ 'content-type': 'application/octet-stream' }, extractUploadHeaders(postage, options)), responseType: 'json', params: { sig: signature } }));
return response.data.reference;
});
}
/**
* create postage batch
* @returns postage batch id
*/
function createPostageBatch(requestOptions, amount, depth, options) {
return __awaiter(this, void 0, void 0, function* () {
const headers = requestOptions.headers || {};
if (options === null || options === void 0 ? void 0 : options.gasPrice) {
headers['gas-price'] = options.gasPrice.toString();
}
if ((options === null || options === void 0 ? void 0 : options.immutableFlag) !== undefined) {
headers.immutable = String(options.immutableFlag);
}
const response = yield http(Object.assign(Object.assign({}, requestOptions), { method: 'post', url: `stamps/${amount}/${depth}`, responseType: 'json', params: { label: options === null || options === void 0 ? void 0 : options.label }, headers }));
if (options === null || options === void 0 ? void 0 : options.waitForUsable) {
const timeout = 100000;
for (let time = 0; time < timeout; time += 3000) {
try {
const batch = yield getPostageBatch(requestOptions, response.data.batchID);
if (batch.usable) {
break;
}
}
catch (e) {
// ignore errors and try again
}
yield new Promise(resolve => setTimeout(resolve, 3000)); // wait 3s
}
}
return response.data.batchID;
});
}
/**
* list postage batches
* @returns postage batch id
*/
function listPostageBatches(requestOptions) {
return __awaiter(this, void 0, void 0, function* () {
const response = yield http(Object.assign(Object.assign({}, requestOptions), { method: 'get', url: 'stamps', responseType: 'json' }));
return response.data.stamps;
});
}
function getPostageBatch(requestOptions, postageBatchId) {
return __awaiter(this, void 0, void 0, function* () {
const response = yield http(Object.assign(Object.assign({}, requestOptions), { method: 'get', url: `stamps/${postageBatchId}`, responseType: 'json' }));
return response.data;
});
}
function getNodeAddresses(requestOptions) {
return __awaiter(this, void 0, void 0, function* () {
const response = yield http(Object.assign(Object.assign({}, requestOptions), { url: 'addresses', responseType: 'json' }));
return response.data;
});
}
function assertSubscriptionHandler(value) {
if (!(0, utils_1.isStrictlyObject)(value)) {
throw new TypeError('SubscriptionHandler has to be object!');
}
const handler = value;
if (typeof handler.onMessage !== 'function') {
throw new TypeError('onMessage property of SubscriptionHandler has to be function!');
}
if (typeof handler.onError !== 'function') {
throw new TypeError('onError property of SubscriptionHandler has to be function!');
}
}
function prepareWebsocketData(data) {
if (typeof data === 'string')
return new TextEncoder().encode(data);
if (data instanceof Buffer)
return new Uint8Array(data);
if (data instanceof ArrayBuffer)
return new Uint8Array(data);
throw new TypeError('unknown websocket data type');
}
/**
* Creates websocket on the given path
*
* @param url Bee node URL
* @param topic Topic name
*/
function webSocket(url, path) {
const wsUrl = url.replace(/^http/i, 'ws');
return new isomorphic_ws_1.default(`${wsUrl}/${path}`);
}
function extractUploadHeaders(postage, options) {
const headers = {};
if ((0, utils_1.isPostageBatchId)(postage)) {
headers['swarm-postage-batch-id'] = postage;
}
else if ((0, utils_1.isPostageStamp)(postage)) {
headers['swarm-postage-stamp'] = postage;
}
else {
throw Error('Postage is invalid. Define either a postage batch id or a postage stamp (coming from envelope)');
}
if (options === null || options === void 0 ? void 0 : options.pin) {
headers['swarm-pin'] = String(options.pin);
}
if (options === null || options === void 0 ? void 0 : options.encrypt) {
headers['swarm-encrypt'] = String(options.encrypt);
}
if (options === null || options === void 0 ? void 0 : options.tag) {
headers['swarm-tag'] = String(options.tag);
}
if (typeof (options === null || options === void 0 ? void 0 : options.deferred) === 'boolean') {
headers['swarm-deferred-upload'] = options.deferred.toString();
}
return headers;
}
/**
* Main function to make HTTP requests.
* @param options User defined settings
* @param config Internal settings and/or Bee settings
*/
function http(config) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
try {
config.headers || (config.headers = {});
config.headers = Object.assign(Object.assign({}, DEFAULT_HTTP_CONFIG.headers), config.headers);
const response = yield (0, axios_1.default)(config);
return response;
}
catch (e) {
if (e instanceof axios_1.AxiosError) {
throw new BeeResponseError(e.message, e.code, e.status, (_a = e.response) === null || _a === void 0 ? void 0 : _a.status, e.config, e.response);
}
throw e;
}
});
}
const DEFAULT_HTTP_CONFIG = {
headers: {
accept: 'application/json, text/plain, */*',
},
maxBodyLength: Infinity,
maxContentLength: Infinity,
};
/** ERRORS */
class BeeError extends Error {
constructor(message) {
super(message);
}
}
class BeeResponseError extends BeeError {
constructor(message, code, axiosStatus, status, config, response) {
super(message);
this.code = code;
this.axiosStatus = axiosStatus;
this.status = status;
this.config = config;
this.response = response;
}
}
;