cosmic-lib
Version:
A JavaScript implementation of the CosmicLink protocol for Stellar
222 lines (192 loc) • 5.77 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
*/
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=${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)
})