cosmic-lib
Version:
A JavaScript implementation of the CosmicLink protocol for Stellar
358 lines (288 loc) • 9.67 kB
JavaScript
;
/**
* 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))]);
};