UNPKG

cosmic-lib

Version:

A JavaScript implementation of the CosmicLink protocol for Stellar

358 lines (288 loc) 9.67 kB
"use strict"; /** * Sep-0007 protocol utilities. * * The methods listed in the documentation are browser-only. * * @see https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0007.md * * @exports sep7Utils */ var _regeneratorRuntime = require("@babel/runtime/regenerator"); var _asyncToGenerator = require("@babel/runtime/helpers/asyncToGenerator"); var sep7Utils = exports; var Buffer = require("@cosmic-plus/base/es5/buffer"); var env = require("@cosmic-plus/jsutils/es5/env"); var StellarSdk = require("@cosmic-plus/base/es5/stellar-sdk"); var _require = require("@cosmic-plus/jsutils/es5/misc"), timeout = _require.timeout; var check = require("./check"); var convert = require("./convert"); var decode = require("./decode"); var parse = require("./parse"); /* Utils */ /** * Proposes the user to register **handler** as her default SEP-0007 link * handler. SEP-0007 requests will hit `{handler}?sep7={sep7Request}`. * * Note that this makes use of a non-standard feature. Compatibility can be * checked using `sep7Utils.isWebHandlerSupported()`. * * [List of compatible browsers](https://caniuse.com/#feat=registerprotocolhandler). * * @example * cosmicLib.sep7Utils.registerWebHandler( * "https://cosmic.link/", * "Cosmic.link" * ) * * @param handler {String} URL of the SEP-0007 web handler. * @param description {String} A brief description of the service. */ sep7Utils.registerWebHandler = function (handler, description) { if (!env.isBrowser) throw new Error("This is a browser-only function."); if (navigator.registerProtocolHandler) { navigator.registerProtocolHandler("web+stellar", "".concat(handler, "?sep7=%s"), description); } else { throw new Error("This browser can't register a SEP-0007 web handler."); } }; /** * Returns whether or not the browser can register a SEP-0007 web handler. * * @example * if (cosmicLib.sep7Utils.isWebHandlerSupported()) { * registerSep7HandlerButton.show() * } * * @returns {Boolean} */ sep7Utils.isWebHandlerSupported = function () { return env.isBrowser && !!navigator.registerProtocolHandler; }; /* Parsing */ /** * Initialize cosmicLink using `sep7Request`. * @private */ sep7Utils.parseRequest = function (cosmicLink, sep7Request, options) { parse.page(cosmicLink, sep7Request); var query = convert.uriToQuery(cosmicLink, sep7Request); var sep7 = decodeURIComponent(query.replace(/^\?(req|sep7)=/, "")); return sep7Utils.parseLink(cosmicLink, sep7, options); }; /** * Initialize cosmicLink using `sep7`. * @private */ sep7Utils.parseLink = function (cosmicLink, sep7) { var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; cosmicLink._sep7 = sep7; if (!options.network) options.network = "public"; if (sep7.substr(12, 4) === "pay?") { cosmicLink.extra.type = "pay"; return sep7Utils.parsePayLink(cosmicLink, sep7, options); } else if (sep7.substr(12, 3) === "tx?") { cosmicLink.extra.type = "tx"; return sep7Utils.parseTxLink(cosmicLink, sep7, options); } else { throw new Error("Invalid SEP-0007 link."); } }; /** * Initialize cosmicLink using `sep7.xdr`. * @private */ sep7Utils.parseTxLink = function (cosmicLink, sep7) { var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var query = convert.uriToQuery(cosmicLink, sep7); var params = query.substr(1).split("&"); var xdr; params.forEach(function (entry) { var field = entry.replace(/=.*$/, ""); var value = entry.substr(field.length + 1); switch (field) { case "xdr": xdr = decodeURIComponent(value); break; case "pubkey": check.address(cosmicLink, value); cosmicLink.extra.pubkey = value; break; default: sep7Utils.parseLinkCommons(cosmicLink, "xdr", field, value, options); } }); if (!xdr) throw new Error("Missing XDR parameter"); options.stripNeutralSequence = true; return { type: "xdr", value: xdr, options: options }; }; /** * Initialize cosmicLink using `sep7.pay`. * @private */ sep7Utils.parsePayLink = function (cosmicLink, sep7) { var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var query = convert.uriToQuery(cosmicLink, sep7); var params = query.substr(1).split("&"); var odesc = { type: "payment" }, asset = {}, memo = {}; var tdesc = { network: options.network, operations: [odesc] }; // Parse params.forEach(function (entry) { var field = entry.replace(/=.*$/, ""); var value = entry.substr(field.length + 1); switch (field) { case "destination": case "amount": odesc[field] = decode.field(cosmicLink, field, value); break; case "asset_code": asset.code = decodeURIComponent(value); break; case "asset_issuer": asset.issuer = decodeURIComponent(value); break; case "memo_type": memo.type = value.split("_")[1].toLowerCase(); break; case "memo": memo.value = decode.string(cosmicLink, value); break; default: sep7Utils.parseLinkCommons(cosmicLink, "pay", field, value, options); } }); // Convert if (memo.type || memo.value) { if (memo.type === "hash" || memo.type === "return") { memo.value = Buffer.from(memo.value, "base64").toString("hex"); } tdesc.memo = memo; } if (asset.code || asset.issuer) odesc.asset = asset; if (!odesc.destination) throw new Error("Missing parameter: destination"); return { type: "tdesc", value: tdesc, options: options }; }; sep7Utils.parseLinkCommons = function (cosmicLink, mode, field, value, options) { switch (field) { case "network_passphrase": options.network = decode.network(cosmicLink, value); break; case "callback": value = decode.url(cosmicLink, value); if (value.substr(0, 4) !== "url:") { throw new Error("Invalid callback: " + value); } options.callback = value.substr(4); break; case "origin_domain": cosmicLink.extra.originDomain = sep7Utils.verifySignature(cosmicLink, value); break; case "signature": cosmicLink.extra.signature = decodeURIComponent(value); break; case "msg": cosmicLink.extra.msg = decode.string(cosmicLink, value); break; default: throw new Error("Invalid SEP-0007 ".concat(mode, " field: ") + field); } }; /* Signing */ sep7Utils.signLink = function (cosmicLink, domain, keypair) { if (!cosmicLink.locker) throw new Error("cosmicLink is not locked."); cosmicLink.extra.originDomain = domain; delete cosmicLink.extra.signature; delete cosmicLink._sep7; var link = cosmicLink.sep7; var payload = sep7Utils.makePayload(link); cosmicLink.extra.signature = keypair.sign(payload).toString("base64"); delete cosmicLink._sep7; }; sep7Utils.verifySignature = /*#__PURE__*/function () { var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(cosmicLink, domain) { var link, signature, payload, keypair; return _regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: link = cosmicLink.sep7.replace(/&signature=.*/, ""); // Let parser parse signature. _context.next = 3; return timeout(1); case 3: signature = cosmicLink.extra.signature; if (signature) { _context.next = 6; break; } throw new Error("No signature attached for domain: ".concat(domain)); case 6: payload = sep7Utils.makePayload(link); _context.next = 9; return sep7Utils.getDomainKeypair(domain); case 9: keypair = _context.sent; if (!keypair.verify(payload, Buffer.from(signature, "base64"))) { _context.next = 14; break; } return _context.abrupt("return", domain); case 14: throw new Error("Invalid signature for domain: ".concat(domain)); case 15: case "end": return _context.stop(); } } }, _callee); })); return function (_x, _x2) { return _ref.apply(this, arguments); }; }(); sep7Utils.getDomainKeypair = /*#__PURE__*/function () { var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(domain) { var toml, signingKey; return _regeneratorRuntime.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: _context2.next = 2; return StellarSdk.StellarTomlResolver.resolve(domain); case 2: toml = _context2.sent; signingKey = toml.URI_REQUEST_SIGNING_KEY; if (signingKey) { _context2.next = 6; break; } throw new Error("Can't find signing key for domain: ".concat(domain)); case 6: return _context2.abrupt("return", StellarSdk.Keypair.fromPublicKey(signingKey)); case 7: case "end": return _context2.stop(); } } }, _callee2); })); return function (_x3) { return _ref2.apply(this, arguments); }; }(); sep7Utils.makePayload = function (link) { return Buffer.concat([Buffer.alloc(35), Buffer.alloc(1, 4), Buffer.from("stellar.sep.7 - URI Scheme".concat(link))]); };