UNPKG

moeralib

Version:

Library to interact with Moera decentralized social network

343 lines (342 loc) 14.7 kB
"use strict"; 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()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MoeraNaming = exports.MoeraNamingConnectionError = exports.MoeraNamingApiError = exports.MoeraNamingError = exports.ANONYMOUS_NODE_PRIVATE_KEY = exports.ANONYMOUS_NODE_PUBLIC_KEY = exports.ANONYMOUS_NODE_NAME = exports.DEV_NAMING_SERVER = exports.MAIN_NAMING_SERVER = void 0; exports.parseNodeName = parseNodeName; exports.shorten = shorten; exports.expand = expand; exports.resolve = resolve; const json_rpc_2_0_1 = require("json-rpc-2.0"); const schema_1 = require("../schema"); const validate_1 = require("./validate"); /** * Main Moera naming server. */ exports.MAIN_NAMING_SERVER = "https://naming.moera.org/moera-naming"; /** * Moera developers' naming server. */ exports.DEV_NAMING_SERVER = "https://naming-dev.moera.org/moera-naming"; /** * Node name used to represent anonymous content author. */ exports.ANONYMOUS_NODE_NAME = "unk_0"; /** * Public key used to verify signatures of anonymous content. */ exports.ANONYMOUS_NODE_PUBLIC_KEY = Buffer.from("VNKYmrV8b1XR7phWvrTUbkfnxsPTKvHIcvJLGrBZQr0Z8eKVdTooBkIWS0CECPjLxPGaQkcKkMcZ6M6wEsJm9A==", "base64"); /** * Private key used to sign anonymous content. */ exports.ANONYMOUS_NODE_PRIVATE_KEY = Buffer.from("tCb+02pUSC6FLdQLPvrUhSBjD0xiV5Q+fQmZY3PKybo=", "base64"); /** * Generic naming server error. */ class MoeraNamingError extends Error { /** * @param {string} method - API method name * @param {string} message - error message */ constructor(method, message) { super(method + ": Naming server error: " + message); } } exports.MoeraNamingError = MoeraNamingError; /** * Naming server returned an error response. */ class MoeraNamingApiError extends MoeraNamingError { /** * @param {string} method - API method name * @param {ErrorResult} result - server response */ constructor(method, result) { super(method, result.error.message); this.errorCode = result.error.code; } } exports.MoeraNamingApiError = MoeraNamingApiError; /** * Naming server connection error. */ class MoeraNamingConnectionError extends Error { /** * @param {string} message - error message */ constructor(message) { super("Naming server connection error: " + message); } } exports.MoeraNamingConnectionError = MoeraNamingConnectionError; /** * Naming API interface. */ class MoeraNaming { /** * @param {string} server - the naming server URL */ constructor(server = exports.MAIN_NAMING_SERVER) { this.rpcClient = new json_rpc_2_0_1.JSONRPCClient((request_1, _a) => __awaiter(this, [request_1, _a], void 0, function* (request, [method, schema]) { let response; try { response = yield fetch(server, { method: "POST", headers: { "accept": "application/json", "content-type": "application/json" }, body: JSON.stringify(request), }); } catch (e) { return Promise.reject(new MoeraNamingConnectionError(String(e))); } let data; try { data = yield response.json(); } catch (e) { if (!response.ok) { return Promise.reject(new MoeraNamingError(method, "Server returned error status")); } else { return Promise.reject(new MoeraNamingError(method, "Server returned empty result")); } } if ("error" in data) { const { valid, errors } = (0, validate_1.validateSchema)("ErrorResult", data); if (!valid) { return Promise.reject(new MoeraNamingError(method, "Server returned invalid error response: " + (0, schema_1.formatSchemaErrors)(errors))); } return Promise.reject(new MoeraNamingApiError(method, data)); } else if (!response.ok && request.id !== undefined) { return Promise.reject(new MoeraNamingError(method, "Server returned invalid error response: " + JSON.stringify(data))); } if (schema == "boolean") { const { valid, errors } = (0, validate_1.validateSchema)("BooleanResult", data); if (!valid) { return Promise.reject(new MoeraNamingError(method, "Server returned invalid boolean response: " + (0, schema_1.formatSchemaErrors)(errors))); } this.rpcClient.receive(data); return; } if (schema == "string") { const { valid, errors } = (0, validate_1.validateSchema)("StringResult", data); if (!valid) { return Promise.reject(new MoeraNamingError(method, "Server returned invalid string response: " + (0, schema_1.formatSchemaErrors)(errors))); } this.rpcClient.receive(data); return; } { const { valid, errors } = (0, validate_1.validateSchema)("ObjectResult", data); if (!valid) { return Promise.reject(new MoeraNamingError(method, "Server returned invalid object response: " + (0, schema_1.formatSchemaErrors)(errors))); } } const result = data.result; if (result == null) { this.rpcClient.receive(data); return; } const { valid, errors } = (0, validate_1.validateSchema)(schema, result); if (!valid) { return Promise.reject(new MoeraNamingError(method, "Server returned invalid object: " + (0, schema_1.formatSchemaErrors)(errors))); } this.rpcClient.receive(data); })); } /** * Register or update the name. See Architecture Overview for the `detailed description * {@link https://moera.org/overview/naming.html} of the algorithm. * * @param {string} name - the name to be registered/updated * @param {number} generation - the name generation to be registered/updated * @param {string | null} updatingKey - the public key for verifying signatures of further updates of the name. May * be ``null`` if the current generation of the name is updated – the current key is preserved in this case. * @param {string | null} nodeUri - URI of the REST API endpoint of the node to which the name is assigned. May be * ``null`` - the current URI is preserved in this case. * @param {string | null} signingKey - the public key of the name owner. May be ``null`` – the current key is * preserved in this case. * @param {number | null} validFrom - the moment in time the owner's key is valid from. May be ``null`` if * ``signingKey`` is also ``null``. * @param {string | null} previousDigest - the unique identifier as reported by a naming server of the current state * of the name. Used to detect the situations when the name was changed by someone else between sending * the request and processing it. May be ``null`` if the name was never registered before. * @param {string | null} signature - the signature, if required, ``null`` otherwise * @return {Promise<string>} identifier of the operation that was created */ put(name_1, generation_1) { return __awaiter(this, arguments, void 0, function* (name, generation, updatingKey = null, nodeUri = null, signingKey = null, validFrom = null, previousDigest = null, signature = null) { return yield this.rpcClient.request("put", { name, generation, updatingKey, nodeUri, signingKey, validFrom, previousDigest, signature }, ["put", "string"]); }); } /** * Get the current status of the operation. * * @param {string} operationId * @return {Promise<OperationStatusInfo |null>} the operation status or ``null``, if the operation ID is unknown */ getStatus(operationId) { return __awaiter(this, void 0, void 0, function* () { return yield this.rpcClient.request("getStatus", { operationId }, ["getStatus", "OperationStatusInfo"]); }); } /** * Get current information about the given generation of the name. * * @param {string} name * @param {number} generation * @return {Promise<RegisteredNameInfo>} the information or ``null``, if the name/generation is not found */ getCurrent(name, generation) { return __awaiter(this, void 0, void 0, function* () { return yield this.rpcClient.request("getCurrent", { name, generation }, ["getCurrent", "RegisteredNameInfo"]); }); } /** * Get past information about the given generation of the name. * * @param {string} name * @param {number} generation * @param {number} at - the moment in time the information is related to * @return {Promise<RegisteredNameInfo | null>} the information or ``null``, if the name/generation did not exist at * the given moment */ getPast(name, generation, at) { return __awaiter(this, void 0, void 0, function* () { return yield this.rpcClient.request("getPast", { name, generation, at }, ["getPast", "RegisteredNameInfo"]); }); } /** * Check if the given name is available for registration. * * @param {string} name * @param {number} generation * @return {Promise<boolean>} ``true``, if the name is free, ``false`` otherwise */ isFree(name, generation) { return __awaiter(this, void 0, void 0, function* () { return yield this.rpcClient.request("isFree", { name, generation }, ["isFree", "boolean"]); }); } /** * Find a name that is close to the given name. * * @param {string} name * @return {Promise<RegisteredNameInfo | null>} information about the name or ``null``, if no name found that is * close enough */ getSimilar(name) { return __awaiter(this, void 0, void 0, function* () { return yield this.rpcClient.request("getSimilar", { name }, ["getSimilar", "RegisteredNameInfo"]); }); } /** * Get the whole history of signing keys for the given name. * * @param {string} name * @param {number} generation * @return {Promise<SigningKeyInfo[]>} the keys */ getAllKeys(name, generation) { return __awaiter(this, void 0, void 0, function* () { return yield this.rpcClient.request("getAllKeys", { name, generation }, ["getAllKeys", "SigningKeyInfoArray"]); }); } /** * Get the list of all registered names at the given moment. The list is returned in pages, one per call. * * @param {number} at - the moment in time the information is related to * @param {number} page - number of the page to be returned (starting from 0) * @param {number} size - size of the page * @return {Promise<RegisteredNameInfo[]>} */ getAll(at, page, size) { return __awaiter(this, void 0, void 0, function* () { return yield this.rpcClient.request("getAll", { at, page, size }, ["getAll", "RegisteredNameInfoArray"]); }); } /** * Get the list of all names registered after the given moment. The list is returned in pages, one per call. * * @param {number} at - the moment in time the information is related to * @param {number} page - number of the page to be returned (starting from 0) * @param {number} size - size of the page * @return {Promise<RegisteredNameInfo[]>} */ getAllNewer(at, page, size) { return __awaiter(this, void 0, void 0, function* () { return yield this.rpcClient.request("getAllNewer", { at, page, size }, ["getAllNewer", "RegisteredNameInfoArray"]); }); } } exports.MoeraNaming = MoeraNaming; /** * Parse a node name and return its name and generation parts. * * If the node name does not include a generation, generation 0 is returned. If name syntax is invalid, ``Error`` * is thrown. * * @param {string} nodeName - the node name to be parsed * @return {[string, number]} [name, generation] */ function parseNodeName(nodeName) { let name = nodeName; let generation = 0; const pos = nodeName.lastIndexOf('_'); if (pos >= 0) { const gen = nodeName.substring(pos + 1); name = nodeName.substring(0, pos); try { generation = parseInt(gen, 10); } catch (error) { throw new Error(`invalid generation: "${gen}"`); } } return [name, generation]; } function shorten(nodeName) { if (nodeName === null) { return null; } const [name, gen] = parseNodeName(nodeName); if (gen === 0) { return name; } else { return nodeName; } } function expand(nodeName) { if (nodeName === null) { return null; } const [name, gen] = parseNodeName(nodeName); return `${name}_${gen}`; } /** * Shortcut function to resolve a node name and get the node URI. * * @param name {string} - the node name * @param namingServer {string} - a naming server to be used * @return {Promise<string | null>} the node URI, or ``null`` if the name does not exist */ function resolve(name_1) { return __awaiter(this, arguments, void 0, function* (name, namingServer = exports.MAIN_NAMING_SERVER) { var _a, _b; const [parsedName, gen] = parseNodeName(name); const naming = new MoeraNaming(namingServer); return (_b = (_a = (yield naming.getCurrent(parsedName, gen))) === null || _a === void 0 ? void 0 : _a.nodeUri) !== null && _b !== void 0 ? _b : null; }); }