emvco-qr-sdk
Version:
A robust TypeScript SDK for decoding, validating, and processing EMVCo-compliant QR codes used in digital payment systems.
67 lines (66 loc) • 3.01 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TlvParserService = void 0;
const qr_errors_constant_1 = require("../../application/constants/qr-errors.constant");
const tlv_entity_1 = require("../entities/tlv.entity");
/**
* Service responsible for parsing a raw TLV-encoded string into structured `TlvEntity` objects.
*
* Supports recursive parsing of nested TLVs (e.g., value fields that themselves contain TLV structures),
* commonly used in QR codes that follow the EMVCo standard.
*/
class TlvParserService {
/**
* Parses a TLV-encoded string into a list of `TlvEntity` instances.
*
* The TLV (Tag-Length-Value) format assumes that:
* - Each tag is 2 characters long.
* - Each length is 2 characters, representing the length of the value in characters.
* - The value follows the length field, and may itself be recursively parsed as a TLV string.
*
* @param tlvString - The raw TLV-encoded string.
* @param recursive - Whether to recursively parse value fields that are TLV-encoded. Defaults to `true`.
*
* @returns An array of parsed `TlvEntity` objects representing the structure of the input string.
*
* @throws {Error} If the input string is invalid (e.g., incomplete, malformed length, or overflows).
*/
static parse(tlvString, recursive = true) {
const result = [];
let index = 0;
while (index < tlvString.length) {
if (index + 4 > tlvString.length) {
throw new Error(qr_errors_constant_1.PARSE_ERRORS.EMPTY_QR);
}
const tag = tlvString.substring(index, index + 2);
index += 2;
const lengthStr = tlvString.substring(index, index + 2);
index += 2;
const length = parseInt(lengthStr, 10);
if (isNaN(length)) {
throw new Error(qr_errors_constant_1.PARSE_ERRORS.INVALID_LENGTH(lengthStr, tag));
}
if (index + length > tlvString.length) {
throw new Error(qr_errors_constant_1.PARSE_ERRORS.EXCEEDS_AVAILABLE(tag));
}
const value = tlvString.substring(index, index + length);
index += length;
let subTags = undefined;
if (recursive && value.length >= 4) {
try {
const parsed = TlvParserService.parse(value, recursive);
const reconstructed = parsed.map((child) => child.toString()).join('');
if (reconstructed === value && parsed.length > 0) {
subTags = parsed;
}
}
catch (error) {
// Nested value is not a valid TLV string — assumed to be raw data (e.g., merchant name).
}
}
result.push(new tlv_entity_1.TlvEntity(tag, value, subTags));
}
return result;
}
}
exports.TlvParserService = TlvParserService;