cosmic-lib
Version:
A JavaScript implementation of the CosmicLink protocol for Stellar
258 lines (212 loc) • 6.09 kB
JavaScript
;
/**
* Decode fields values from URI to cosmic link JSON format. This format differs
* from Stellar transaction format: it is simpler, allow for federated address
* and can be stringified/parsed without loss of information.
*
* For each of those functions, any error is recorded in the `conf` object
* and HTML nodes are updated accordingly.
*
* @private
* @exports decode
*/
require("core-js/modules/es.string.replace.js");
const decode = exports;
const check = require("./check");
const normalize = require("./normalize");
const specs = require("./specs");
const status = require("./status");
decode.query = function (conf, query = "?") {
if (query.substr(0, 1) !== "?") status.fail(conf, "Invalid query", "throw");
query = query.substr(1);
const operations = [];
const tdesc = {}; // Backward compatibility with the old non-standard syntax, deprecated since
// 2019-08-26. This adds `type=` at the beginning of the query when the first
// parameter doesn't contains an `=` sign.
if (query.match(/^\w+(&|$)/)) query = "type=".concat(query);
let parser;
const params = query.split("&");
for (let index in params) {
const entry = params[index];
const field = entry.split("=", 1)[0];
const value = entry.substring(field.length + 1);
if (!field) continue;
if (field === "type") {
if (parser) {
status.error(conf, "Query declares 'type' several times", "throw");
} else if (value !== "transaction") {
operations[0] = {
type: value
};
}
parser = value;
continue;
} else if (!parser) {
status.error(conf, "Query doesn't declare 'type' in first position", "throw");
}
if (field === "operation") {
operations.push({
type: value
});
parser = "operation";
continue;
}
const decoded = decode.field(conf, field, value); /// Multi-operation link.
if (parser === "transaction") {
tdesc[field] = decoded;
} else if (parser === "operation") {
operations[operations.length - 1][field] = decoded; /// One-operation link.
} else {
if (specs.isTransactionField(field)) {
tdesc[field] = decoded;
} else {
operations[0][field] = decoded;
}
}
}
tdesc.operations = operations;
normalize.tdesc(conf, tdesc);
tdesc.operations.forEach(odesc => normalize.odesc(conf, odesc));
check.tdesc(conf, tdesc);
return tdesc;
};
/******************************************************************************/
/**
* Decode `value` accordingly to `field` type.
*
* @param {string} field
* @param {string} value
*/
decode.field = function (conf, field, value) {
const type = specs.fieldType[field];
return type ? decode.type(conf, type, value) : value;
};
/**
* Decode `value` using the decoding function for `type`.
*
* @param {string} type
* @param {string} value
*/
decode.type = function (conf, type, value) {
if (value) {
value = decodeURIComponent(value);
return process[type] ? process[type](conf, value) : value;
} else {
return "";
}
};
/******************************************************************************/
const process = {};
process.asset = function (conf, asset) {
const assetLower = asset.toLowerCase();
if (assetLower === "xlm" || assetLower === "native") {
return {
code: "XLM"
};
} else {
const temp = asset.split(":");
const object = {
code: temp[0],
issuer: temp[1]
};
return object;
}
};
process.assetsArray = function (conf, assetsList) {
const strArray = assetsList.split(",");
return strArray.map(entry => decode.asset(conf, entry));
};
process.authorizeFlag = function (conf, flag) {
// Backward compatibility (protocol =< 12)
switch (flag) {
case "false":
return 0;
case "true":
return 1;
}
return Number(flag);
};
process.boolean = function (conf, string) {
switch (string) {
case "true":
return true;
case "false":
return false;
}
};
process.buffer = function (conf, string) {
const matched = string.match(/(^[^:]*):/);
const type = matched && matched[1];
switch (type) {
case "base64":
return {
type: type,
value: string.substr(type.length + 1)
};
case "text":
string = string.substr(type.length + 1);
// Fallthrough
default:
return {
type: "text",
value: process.string(conf, string)
};
}
};
process.date = function (conf, string) {
/// now + {minutes} syntactic sugar
if (string.match(/^\+[0-9]+$/)) return string; /// Use UTC timezone by default.
if (string.match(/T[^+]*[0-9]$/)) string += "Z";
return new Date(string).toISOString();
};
process.memo = function (conf, string) {
const matched = string.match(/(^[^:]*):/);
const type = matched && matched[1];
switch (type) {
case "base64":
case "id":
case "hash":
case "return":
return {
type: type,
value: string.substr(type.length + 1)
};
case "text":
string = string.substr(type.length + 1);
// Fallthrough
default:
return {
type: "text",
value: process.string(conf, string)
};
}
};
process.price = function (conf, price) {
const numerator = price.replace(/:.*/, "");
const denominator = price.replace(/^[^:]*:/, "");
if (numerator === denominator) return price;else return {
n: +numerator,
d: +denominator
};
};
process.signer = function (conf, signer) {
const temp = signer.split(":");
const object = {
weight: temp[0],
type: temp[1],
value: temp[2]
};
return object;
};
process.string = function (conf, string) {
return string.replace(/\+/g, " ");
};
process.network = process.string;
/******************************************************************************/
/**
* Provide dummy aliases for every other type for convenience & backward
* compatibility.
*/
specs.types.forEach(type => {
exports[type] = (conf, value) => decode.type(conf, type, value);
});