From 5dabe4d41073a09c1b1a1ff3e3e4b14d88708ca9 Mon Sep 17 00:00:00 2001 From: srikanth716 Date: Fri, 19 Dec 2025 18:22:57 +0530 Subject: [PATCH 01/23] [INJIVER-1512] JS claim-169 qrcode mapper Signed-off-by: srikanth716 --- js/package-lock.json | 13 +- js/package.json | 2 +- js/src/index.js | 215 +++++++++++--------- js/src/shared/Constants.js | 183 ++++++++++++++++- js/src/utils/cborUtils.js | 170 ++++++++++++++++ js/src/utils/mapperUtils.js | 76 +++++++ js/test/PixelPass.test.js | 231 --------------------- js/test/UtilsTest.test.js | 333 +++++++++++++++++++++++++++++++ js/test/decode.test.js | 36 ++++ js/test/decodeBinary.test.js | 38 ++++ js/test/decodeMappedData.test.js | 52 +++++ js/test/generateQRCode.test.js | 26 +++ js/test/generateQRData.test.js | 32 +++ js/test/getMappedData.test.js | 73 +++++++ 14 files changed, 1148 insertions(+), 332 deletions(-) create mode 100644 js/src/utils/cborUtils.js create mode 100644 js/src/utils/mapperUtils.js delete mode 100644 js/test/PixelPass.test.js create mode 100644 js/test/UtilsTest.test.js create mode 100644 js/test/decode.test.js create mode 100644 js/test/decodeBinary.test.js create mode 100644 js/test/decodeMappedData.test.js create mode 100644 js/test/generateQRCode.test.js create mode 100644 js/test/generateQRData.test.js create mode 100644 js/test/getMappedData.test.js diff --git a/js/package-lock.json b/js/package-lock.json index f6ca70d..f7a89dc 100644 --- a/js/package-lock.json +++ b/js/package-lock.json @@ -1,12 +1,12 @@ { "name": "@mosip/pixelpass", - "version": "0.7.0", + "version": "0.8.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@mosip/pixelpass", - "version": "0.7.0", + "version": "0.8.0", "license": "MPL-2.0", "dependencies": { "base45-web": "^1.0.2", @@ -5591,8 +5591,7 @@ "version": "7.21.0-placeholder-for-preset-env.2", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true, - "requires": {} + "dev": true }, "@babel/plugin-syntax-async-generators": { "version": "7.8.4", @@ -7203,8 +7202,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", - "dev": true, - "requires": {} + "dev": true }, "deepmerge": { "version": "4.3.1", @@ -7838,8 +7836,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "requires": {} + "dev": true }, "jest-regex-util": { "version": "29.6.3", diff --git a/js/package.json b/js/package.json index 18badeb..2376f64 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "@mosip/pixelpass", - "version": "0.7.0", + "version": "0.8.0", "repository": "https://github.com/mosip/pixelpass", "author": "MOSIP", "license": "MPL-2.0", diff --git a/js/src/index.js b/js/src/index.js index 7ee15a6..7f4c7c3 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -1,116 +1,149 @@ const { - DEFAULT_QR_QUALITY, - DEFAULT_QR_BORDER, - DEFAULT_QR_SCALE, - COLOR_BLACK, - COLOR_WHITE, - DEFAULT_ZLIB_COMPRESSION_LEVEL, - DEFAULT_ECC_LEVEL, - ZIP_HEADER, - DEFAULT_ZIP_FILE_NAME -} = require('./shared/Constants'); -const QRCode = require('qrcode'); + DEFAULT_QR_QUALITY, + DEFAULT_QR_BORDER, + DEFAULT_QR_SCALE, + COLOR_BLACK, + COLOR_WHITE, + DEFAULT_ZLIB_COMPRESSION_LEVEL, + DEFAULT_ECC_LEVEL, + ZIP_HEADER, + DEFAULT_ZIP_FILE_NAME, + CLAIM_169_KEY_MAPPER, + CLAIM_169_VALUE_MAPPER, + CLAIM_169_REVERSE_KEY_MAPPER, +} = require("./shared/Constants"); +const QRCode = require("qrcode"); const b45 = require("base45-web"); const pako = require("pako"); const cbor = require("cbor-web"); const JSZip = require("jszip"); +const { + toJson, + replaceKeysAtDepth, + replaceValuesForClaim169, +} = require("./utils/cborUtils.js"); +const { toMapWithKeyAndValueMapper } = require("./utils/mapperUtils.js"); function generateQRData(data, header = "") { - let parsedData = null; - let compressedData, b45EncodedData; - try { - parsedData = JSON.parse(data); - const cborEncodedData = cbor.encode(parsedData); - compressedData = pako.deflate(cborEncodedData, {level: DEFAULT_ZLIB_COMPRESSION_LEVEL}); - } catch (e) { - console.error("Data is not JSON"); - compressedData = pako.deflate(data, {level: DEFAULT_ZLIB_COMPRESSION_LEVEL}); - } finally { - b45EncodedData = b45.encode(compressedData).toString(); - } - return header + b45EncodedData; + let parsedData = null; + let compressedData, b45EncodedData; + try { + parsedData = JSON.parse(data); + const cborEncodedData = cbor.encode(parsedData); + compressedData = pako.deflate(cborEncodedData, { + level: DEFAULT_ZLIB_COMPRESSION_LEVEL, + }); + } catch (e) { + console.error("Data is not JSON"); + compressedData = pako.deflate(data, { + level: DEFAULT_ZLIB_COMPRESSION_LEVEL, + }); + } finally { + b45EncodedData = b45.encode(compressedData).toString(); + } + return header + b45EncodedData; } + async function generateQRCode(data, ecc = DEFAULT_ECC_LEVEL, header = "") { - const base45Data = generateQRData(data, header); - const opts = { - errorCorrectionLevel: ecc, - quality: DEFAULT_QR_QUALITY, - margin: DEFAULT_QR_BORDER, - scale: DEFAULT_QR_SCALE, - color: { - dark: COLOR_BLACK, - light: COLOR_WHITE, - }, - }; - return QRCode.toDataURL(base45Data, opts); + const base45Data = generateQRData(data, header); + const opts = { + errorCorrectionLevel: ecc, + quality: DEFAULT_QR_QUALITY, + margin: DEFAULT_QR_BORDER, + scale: DEFAULT_QR_SCALE, + color: { + dark: COLOR_BLACK, + light: COLOR_WHITE, + }, + }; + return QRCode.toDataURL(base45Data, opts); } function decode(data) { - const decodedBase45Data = b45.decode(data); - const decompressedData = pako.inflate(decodedBase45Data); - const textData = new TextDecoder().decode(decompressedData); - try { - const decodedCBORData = cbor.decode(decompressedData); - if (decodedCBORData) return JSON.stringify(decodedCBORData); - return textData; - } catch (e) { - return textData; - } + const decodedBase45Data = b45.decode(data); + const decompressedData = pako.inflate(decodedBase45Data); + const textData = new TextDecoder().decode(decompressedData); + try { + const decodedCBORData = cbor.decode(decompressedData); + if (decodedCBORData) return JSON.stringify(decodedCBORData); + return textData; + } catch (e) { + return textData; + } } async function decodeBinary(data) { - let decodedData = new TextDecoder("utf-8").decode(data); - if (decodedData.startsWith(ZIP_HEADER)){ - return (await JSZip.loadAsync(decodedData)).file(DEFAULT_ZIP_FILE_NAME).async("text") - }else { - throw new Error("Unsupported binary file type"); - } + let decodedData = new TextDecoder("utf-8").decode(data); + if (decodedData.startsWith(ZIP_HEADER)) { + return (await JSZip.loadAsync(decodedData)) + .file(DEFAULT_ZIP_FILE_NAME) + .async("text"); + } else { + throw new Error("Unsupported binary file type"); + } } -function getMappedData(jsonData, mapper, cborEnable = false) { - const payload ={}; - for (const param in jsonData) { - const key = mapper[param] ? mapper[param] : param; - payload[key]= jsonData[param]; - } - if (cborEnable) - return cbor.encode(payload); - else - return payload -} +function getMappedData( + jsonData, + keyMapper = CLAIM_169_KEY_MAPPER, + valueMapper = CLAIM_169_VALUE_MAPPER, + cborEnable = false +) { + if (Array.isArray(jsonData)) { + return jsonData.map((item) => + getMappedData(item, keyMapper, valueMapper, cborEnable) + ); + } -function decodeMappedData(data, mapper) { - try { - const jsonData = cbor.decode(data) - return translateToJSON(jsonData, mapper) - }catch (e) { - return translateToJSON(data,mapper) - } + const payload = toMapWithKeyAndValueMapper( + jsonData, + keyMapper, + valueMapper + ); -} + if (cborEnable) { + return Buffer.from(cbor.encode(payload)).toString("hex"); + } -function translateToJSON(claims, mapper) { - const result = {} - if (claims instanceof Map) { - claims.forEach((value, param) => { - const key = mapper[param] ? mapper[param] : param; - result[key] = value; - }); - } else if (typeof claims === 'object' && claims !== null) { - Object.entries(claims).forEach(([param, value]) => { - const key = mapper[param] ? mapper[param] : param; - result[key] = value; - }); - } - return result; + return payload; } +function decodeMappedData( + data, + keyMapper = CLAIM_169_REVERSE_KEY_MAPPER, + valueMapperFunction = replaceValuesForClaim169 +) { + if (Array.isArray(data)) { + return data.map((item, i) => { + return decodeMappedData(item, keyMapper, valueMapperFunction); + }); + } + + let jsonData; + try { + const bytes = Buffer.from(data, "hex"); + const decoded = cbor.decode(bytes); + jsonData = toJson(decoded); + } catch (error) { + jsonData = JSON.parse(data); + } + + keyMapper.forEach((mapper, index) => { + jsonData = replaceKeysAtDepth(jsonData, mapper, index); + }); + + if (valueMapperFunction) { + jsonData = valueMapperFunction(jsonData); + } + + return JSON.stringify(jsonData); +} module.exports = { - generateQRData, - generateQRCode, - decode, - getMappedData, - decodeMappedData, - decodeBinary + generateQRData, + generateQRCode, + decode, + decodeBinary, + getMappedData, + decodeMappedData }; diff --git a/js/src/shared/Constants.js b/js/src/shared/Constants.js index a77c4d1..7cf3274 100644 --- a/js/src/shared/Constants.js +++ b/js/src/shared/Constants.js @@ -8,4 +8,185 @@ exports.DEFAULT_QR_SCALE = 10 exports.DEFAULT_QR_BORDER = 3 exports.DEFAULT_QR_QUALITY = 1 exports.ZIP_HEADER = "PK" -exports.DEFAULT_ZIP_FILE_NAME = "certificate.json" \ No newline at end of file +exports.DEFAULT_ZIP_FILE_NAME = "certificate.json" + +exports.CLAIM_169_KEY_MAPPER = { + "ID": 1, + "Version": 2, + "Language": 3, + "Full Name": 4, + "First Name": 5, + "Middle Name": 6, + "Last Name": 7, + "Date of Birth": 8, + "Gender": 9, + "Address": 10, + "Email ID": 11, + "Phone Number": 12, + "Nationality": 13, + "Marital Status": 14, + "Guardian": 15, + "Binary Image": 16, + "Binary Image Format": 17, + "Best Quality Fingers": 18, + + "Right Thumb": 50, + "Right Pointer Finger": 51, + "Right Middle Finger": 52, + "Right Ring Finger": 53, + "Right Little Finger": 54, + "Left Thumb": 55, + "Left Pointer Finger": 56, + "Left Middle Finger": 57, + "Left Ring Finger": 58, + "Left Little Finger": 59, + "Right Iris": 60, + "Left Iris": 61, + "Face": 62, + "Right Palm Print": 63, + "Left Palm Print": 64, + "Voice": 65, + + "Data": 0, + "Data format": 1, + "Data sub format": 2, + "Data issuer": 3 +}; + +exports.CLAIM_169_VALUE_MAPPER = { + "Data format": { + "Image": 0, + "Template": 1, + "Sound": 2, + "Bio Hash": 3 + }, + "Data sub format": { + "PNG": 0, + "JPEG": 1, + "JPEG2000": 2, + "AVIF": 3, + "WEBP": 4, + "TIFF": 5, + "WSQ": 6, + + "Fingerprint Template ANSI 378": 0, + "Fingerprint Template ISO 19794-2": 1, + "Fingerprint Template NIST": 2, + + "WAV": 0, + "MP3": 1 + }, + "Gender": { + "Male": 1, + "Female": 2, + "Others": 3 + }, + "Marital Status": { + "Unmarried": 1, + "Married": 2, + "Divorced": 3 + }, + "Binary Image Format": { + "JPEG": 1, + "JPEG2": 2, + "AVIF": 3, + "WEBP": 4 + } +}; + +exports.CLAIM_169_REVERSE_KEY_MAPPER = [ + { + "1": "ID", + "2": "Version", + "3": "Language", + "4": "Full Name", + "5": "First Name", + "6": "Middle Name", + "7": "Last Name", + "8": "Date of Birth", + "9": "Gender", + "10": "Address", + "11": "Email ID", + "12": "Phone Number", + "13": "Nationality", + "14": "Marital Status", + "15": "Guardian", + "16": "Binary Image", + "17": "Binary Image Format", + "18": "Best Quality Fingers", + + "50": "Right Thumb", + "51": "Right Pointer Finger", + "52": "Right Middle Finger", + "53": "Right Ring Finger", + "54": "Right Little Finger", + "55": "Left Thumb", + "56": "Left Pointer Finger", + "57": "Left Middle Finger", + "58": "Left Ring Finger", + "59": "Left Little Finger", + "60": "Right Iris", + "61": "Left Iris", + "62": "Face", + "63": "Right Palm Print", + "64": "Left Palm Print", + "65": "Voice" + }, + { + "0": "Data", + "1": "Data format", + "2": "Data sub format", + "3": "Data issuer" + } +]; + +exports.CLAIM_169_ROOT_REVERSE_VALUE_MAPPER = { + "Data format": { 0: "Image", 1: "Template", 2: "Sound", 3: "Bio Hash" }, + "Gender": { 1: "Male", 2: "Female", 3: "Others" }, + "Binary Image Format": { 1: "JPEG", 2: "JPEG2", 3: "AVIF", 4: "WEBP" }, + "Marital Status": { 1: "Unmarried", 2: "Married", 3: "Divorced" } +}; + +exports.CLAIM_169_BIOMETRIC_FORMAT_REVERSE_VALUE_MAPPER = { + 0: "Image", + 1: "Template", + 2: "Sound", + 3: "Bio Hash" +}; + +exports.CLAIM_169_BIOMETRIC_SUB_FORMAT_REVERSE_VALUE_MAPPER = { + "Image": { + 0: "PNG", 1: "JPEG", 2: "JPEG2000", 3: "AVIF", 4: "WEBP", 5: "TIFF", 6: "WSQ" + }, + "Template": { + 0: "Fingerprint Template ANSI 378", + 1: "Fingerprint Template ISO 19794-2", + 2: "Fingerprint Template NIST" + }, + "Sound": { + 0: "WAV", + 1: "MP3" + } +}; + +exports.CLAIM_169_BIOMETRIC_KEYS = [ + "Right Thumb", + "Right Pointer Finger", + "Right Middle Finger", + "Right Ring Finger", + "Right Little Finger", + "Left Thumb", + "Left Pointer Finger", + "Left Middle Finger", + "Left Ring Finger", + "Left Little Finger", + "Right Iris", + "Left Iris", + "Face", + "Right Palm Print", + "Left Palm Print", + "Voice" +]; + +exports.CLAIM_169_BIOMETRIC_DATA_FORMAT_KEY = "Data format"; +exports.CLAIM_169_BIOMETRIC_DATA_SUB_FORMAT_KEY = "Data sub format"; \ No newline at end of file diff --git a/js/src/utils/cborUtils.js b/js/src/utils/cborUtils.js new file mode 100644 index 0000000..89ab080 --- /dev/null +++ b/js/src/utils/cborUtils.js @@ -0,0 +1,170 @@ +const { + CLAIM_169_BIOMETRIC_KEYS, + CLAIM_169_BIOMETRIC_DATA_FORMAT_KEY, + CLAIM_169_BIOMETRIC_DATA_SUB_FORMAT_KEY, + CLAIM_169_BIOMETRIC_FORMAT_REVERSE_VALUE_MAPPER, + CLAIM_169_BIOMETRIC_SUB_FORMAT_REVERSE_VALUE_MAPPER, + CLAIM_169_ROOT_REVERSE_VALUE_MAPPER, +} = require("../shared/Constants"); + +function toJson(value) { + if (value instanceof Map) { + const data = {}; + value.forEach((mapValue, mapKey) => { + data[mapKey] = toJson(mapValue); + }); + return data; + } + + if (Array.isArray(value)) { + return value.map(toJson); + } + + if (typeof value === "object" && value !== null) { + const data = {}; + for (const [key, val] of Object.entries(value)) { + data[key] = toJson(val); + } + return data; + } + + return value; +} + +function replaceKeysAtDepth(obj, mapper, depth, currentDepth = 0) { + if (Array.isArray(obj)) { + return replaceKeysInArrayAtDepth(obj, mapper, depth, currentDepth); + } + + if (typeof obj !== "object" || obj === null) { + return obj; + } + + const result = {}; + const nextDepth = currentDepth + 1; + + for (const [originalKey, value] of Object.entries(obj)) { + const newKey = + currentDepth === depth + ? mapper[originalKey] ?? mapper[Number(originalKey)] ?? originalKey + : originalKey; + + let processedValue; + if (Array.isArray(value)) { + processedValue = replaceKeysInArrayAtDepth( + value, + mapper, + depth, + nextDepth + ); + } else if (typeof value === "object" && value !== null) { + processedValue = replaceKeysAtDepth(value, mapper, depth, nextDepth); + } else if (value === null) { + processedValue = null; + } else { + processedValue = value; + } + + result[newKey] = processedValue; + } + + return result; +} + +function replaceKeysInArrayAtDepth(arr, mapper, depth, currentDepth = 0) { + return arr.map((item) => { + if (Array.isArray(item)) { + return replaceKeysInArrayAtDepth(item, mapper, depth, currentDepth); + } else if (typeof item === "object" && item !== null) { + return replaceKeysAtDepth(item, mapper, depth, currentDepth); + } else { + return item; + } + }); +} + +function replaceValuesForClaim169(jsonData) { + const result = { ...jsonData }; + + Object.entries(CLAIM_169_ROOT_REVERSE_VALUE_MAPPER).forEach( + ([fieldName, reverseMap]) => { + if (result[fieldName] !== undefined) { + const originalValue = result[fieldName]; + const mappedValue = reverseMap[originalValue]; + if (mappedValue !== undefined) { + result[fieldName] = mappedValue; + } + } + } + ); + + CLAIM_169_BIOMETRIC_KEYS.forEach((nestedKey) => { + if (result[nestedKey] === undefined) return; + + const nestedObject = result[nestedKey]; + if ( + typeof nestedObject !== "object" || + nestedObject === null || + Array.isArray(nestedObject) + ) { + return; + } + + const formatKey = CLAIM_169_BIOMETRIC_DATA_FORMAT_KEY; + const subFormatKey = CLAIM_169_BIOMETRIC_DATA_SUB_FORMAT_KEY; + + if ( + nestedObject[formatKey] === undefined || + nestedObject[subFormatKey] === undefined + ) { + return; + } + + const dataFormatShortCode = nestedObject[formatKey]; + const subFormatShortCode = nestedObject[subFormatKey]; + + const dataFormatInt = + typeof dataFormatShortCode === "number" + ? dataFormatShortCode + : typeof dataFormatShortCode === "string" + ? parseInt(dataFormatShortCode, 10) + : null; + + const subFormatInt = + typeof subFormatShortCode === "number" + ? subFormatShortCode + : typeof subFormatShortCode === "string" + ? parseInt(subFormatShortCode, 10) + : null; + + if (dataFormatInt === null || Number.isNaN(dataFormatInt)) return; + + const dataFormatValue = + CLAIM_169_BIOMETRIC_FORMAT_REVERSE_VALUE_MAPPER[dataFormatInt]; + + const subFormatValue = + dataFormatValue !== undefined && subFormatInt !== null + ? CLAIM_169_BIOMETRIC_SUB_FORMAT_REVERSE_VALUE_MAPPER?.[ + dataFormatValue + ]?.[subFormatInt] + : undefined; + + if (dataFormatValue !== undefined) { + nestedObject[formatKey] = dataFormatValue; + } + + if (subFormatValue !== undefined) { + nestedObject[subFormatKey] = subFormatValue; + } + + result[nestedKey] = nestedObject; + }); + + return result; +} + +module.exports = { + toJson, + replaceKeysAtDepth, + replaceValuesForClaim169, +}; diff --git a/js/src/utils/mapperUtils.js b/js/src/utils/mapperUtils.js new file mode 100644 index 0000000..5f2ef11 --- /dev/null +++ b/js/src/utils/mapperUtils.js @@ -0,0 +1,76 @@ +function toMapWithKeyAndValueMapper(data, keyMapper, valueMapper) { + if (data === null || data === undefined || typeof data !== "object") + return data; + + const result = {}; + + for (const [key, value] of Object.entries(data)) { + const mappedKey = keyMapper[key] ?? key; + + let processedValue = value; + const fieldValueMapper = valueMapper?.[key]; + if ( + value !== null && + fieldValueMapper && + fieldValueMapper[value] !== undefined + ) { + processedValue = fieldValueMapper[value]; + } + + if (processedValue === null) { + result[mappedKey] = null; + } else if (Array.isArray(processedValue)) { + result[mappedKey] = toListWithKeyAndValueMapper( + processedValue, + keyMapper, + valueMapper + ); + } else if (typeof processedValue === "object") { + result[mappedKey] = toMapWithKeyAndValueMapper( + processedValue, + keyMapper, + valueMapper + ); + } else { + result[mappedKey] = processedValue; + } + } + + return result; +} + +function toListWithKeyAndValueMapper(arr, keyMapper, valueMapper) { + const result = []; + + for (let i = 0; i < arr.length; i++) { + const value = arr[i]; + let processedValue; + + if (value === null) { + processedValue = null; + } else if (typeof value === "object" && !Array.isArray(value)) { + processedValue = toMapWithKeyAndValueMapper( + value, + keyMapper, + valueMapper + ); + } else if (Array.isArray(value)) { + processedValue = toListWithKeyAndValueMapper( + value, + keyMapper, + valueMapper + ); + } else { + processedValue = value; + } + + result.push(processedValue); + } + + return result; +} + +module.exports = { + toMapWithKeyAndValueMapper, + toListWithKeyAndValueMapper, +}; diff --git a/js/test/PixelPass.test.js b/js/test/PixelPass.test.js deleted file mode 100644 index 1239820..0000000 --- a/js/test/PixelPass.test.js +++ /dev/null @@ -1,231 +0,0 @@ -const {decode, decodeBinary, generateQRCode, generateQRData, getMappedData, decodeMappedData} = require("../src"); -const {expect} = require("expect"); -const {ECC} = require("../src/types/ECC"); -const JSZip = require("jszip"); - -const HEX_ENCODING = "hex"; - - -test("should return decoded data for given QR data", () => { - const data = "NCFKVPV0QSIP600GP5L0"; - const expected = "hello"; - - const actual = decode(data); - expect(actual).toBe(expected); -}); - -test("should return decoded data for given QR data for zipped data", async () => { - const expected = "Hello World!!"; - const zip = new JSZip(); - zip.file("certificate.json", expected, { - compression: "DEFLATE" - }); - const data = await zip.generateAsync({type: 'string', compression: "DEFLATE"}) - - const actual = await decodeBinary(new TextEncoder().encode(data)); - expect(actual).toBe(expected); -},5000); - - -test("should throw error if binary data type not zip", async () => { - await expect(decodeBinary(new TextEncoder().encode("tempfile"))).rejects.toThrowError("Unsupported binary file type") -}); - - -test("should return decoded data for given QR data in cbor", async () => { - const data = "NCF3QBXJA5NJRCOC004 QN4"; - const expected = "{\"temp\":15}"; - const actual = decode(data); - expect(actual).toBe(expected); -}); -test("should throw error if given data is undefined for encoding", () => { - expect(() => generateQRData(undefined)).toThrowError( - "byteArrayArg is null or undefined." - ); -}); -test("should throw error if given data length is bad", () => { - expect(() => decode("1")).toThrowError("utf8StringArg has incorrect length."); - expect(() => decode("1234")).toThrowError( - "utf8StringArg has incorrect length." - ); -}); -test("should throw error if given data is invalid", () => { - expect(() => decode("^1")).toThrowError("Invalid character at position 0."); - expect(() => decode("1^")).toThrowError("Invalid character at position 1."); - expect(() => decode("0123456789^")).toThrowError( - "Invalid character at position 10." - ); -}); - -test("should return encoded QR data for data", () => { - const expected = "NCFKVPV0QSIP600GP5L0"; - const data = "hello"; - - const actual = generateQRData(data); - expect(actual).toBe(expected); -}); - -test("should return encoded QR data for given data with cbor", () => { - const expected = "NCF3QBXJA5NJRCOC004 QN4"; - const data = '{"temp":15}'; - const actual = generateQRData(data); - expect(actual).toBe(expected); -}); -test("should return encoded QR data for data with header", () => { - const expected = "mockHeader://" + "NCFKVPV0QSIP600GP5L0"; - const data = "hello"; - - const actual = generateQRData(data, "mockHeader://"); - expect(actual).toBe(expected); -}); -test("should return base64 encoded QR for given data", async () => { - const data = "hello"; - const expected = - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQ4AAAEOCAYAAAB4sfmlAAAAAklEQVR4AewaftIAAAUpSURBVO3BQY5bCw4EsCrB97+yJvvePH14YKdDsvtHAA4mAEcTgKMJwNEE4GgCcDQBOJoAHE0AjiYARxOAownA0QTgaAJwNAE4mgAcTQCOJgBHE4CjCcDRBOBoAnA0ATiaABxNAI4mAEcTgKMJwNEE4GgCcPTKh7QNP+1uPqFtntjdPNE2T+xunmgbftrdfMIE4GgCcDQBOJoAHE0AjiYARxOAownA0QTg6JUvt7v5DdrmndrmnXY3v8Hu5jdom282ATiaABxNAI4mAEcTgKMJwNEE4GgCcDQBOHrll2ibT9jdfMLu5p3a5p12N9+sbT5hd/MbTACOJgBHE4CjCcDRBOBoAnA0ATiaABxNAI5e4Vdrmyd2N+/UNk/sbvj7TACOJgBHE4CjCcDRBOBoAnA0ATiaABxNAI5e4a/UNu/UNk/sbmACcDQBOJoAHE0AjiYARxOAownA0QTgaAJw9Movsbv5l+xuPqFtntjdfLPdDf/dBOBoAnA0ATiaABxNAI4mAEcTgKMJwNEE4OiVL9c2/NQ2T+xunmibJ3Y3T7TNE7ubd2ob/v8mAEcTgKMJwNEE4GgCcDQBOJoAHE0AjiYAR90/wj+vbT5hd8PfZwJwNAE4mgAcTQCOJgBHE4CjCcDRBOBoAnD0yoe0zRO7myfa5ondzRNt88Tu5om2eWJ38812N0+0zTdrmyd2N+/UNk/sbr7ZBOBoAnA0ATiaABxNAI4mAEcTgKMJwNEE4Kj7Rz6gbd5pd/NE2zyxu/mEtnlid/NE2zyxu3mibf4lu5tPaJsndjefMAE4mgAcTQCOJgBHE4CjCcDRBOBoAnA0ATh65cvtbp5om09om09omyd2N0+0zSfsbp5om09oG36aABxNAI4mAEcTgKMJwNEE4GgCcDQBOJoAHHX/CD+0zTvtbp5omyd2N9+sbZ7Y3bxT27zT7oafJgBHE4CjCcDRBOBoAnA0ATiaABxNAI4mAEev/GPa5ondzTu1zTu1zRO7m3dqm99gd/PN2uaJ3c0nTACOJgBHE4CjCcDRBOBoAnA0ATiaABxNAI66f4S/Ttv8Brubd2qbd9rdPNE277S7+WYTgKMJwNEE4GgCcDQBOJoAHE0AjiYARxOAo+4f+YC24afdzSe0zRO7m09omyd2N+/UNk/sbp5om3fa3XzCBOBoAnA0ATiaABxNAI4mAEcTgKMJwNEE4OiVL7e7+Q3a5hPa5p3a5hN2N0+0zRO7myd2N/w0ATiaABxNAI4mAEcTgKMJwNEE4GgCcDQBOHrll2ibT9jdfELbPLG7eae24b/b3fwGE4CjCcDRBOBoAnA0ATiaABxNAI4mAEcTgKNX+Cvtbp5omyd2N++0u3mibZ5omyd2N+/UNk/sbp5omyd2N99sAnA0ATiaABxNAI4mAEcTgKMJwNEE4GgCcPQKf6W2eae2eae2eWJ380Tb/Aa7myfa5ondzSdMAI4mAEcTgKMJwNEE4GgCcDQBOJoAHE0Ajl75JXY3/5LdzSe0zRO7myfa5ondzTu1zSe0zW8wATiaABxNAI4mAEcTgKMJwNEE4GgCcDQBOHrly7UNP7XNJ+xu3ml380TbPLG7eWJ38wm7myfa5ptNAI4mAEcTgKMJwNEE4GgCcDQBOJoAHE0Ajrp/BOBgAnA0ATiaABxNAI4mAEcTgKMJwNEE4GgCcDQBOJoAHE0AjiYARxOAownA0QTgaAJwNAE4mgAcTQCOJgBHE4CjCcDRBOBoAnA0ATiaABxNAI4mAEf/A4+rCzKLi4oKAAAAAElFTkSuQmCC"; - - const actual = await generateQRCode(data, ECC.M); - expect(actual).toBe(expected); -},5000); -test("should return base64 encoded QR for given data with header", async () => { - const data = "hello"; - const header = "mockHeader://"; - const expected = - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAV4AAAFeCAYAAADNK3caAAAAAklEQVR4AewaftIAAAi9SURBVO3BQZIkB44EMHda/f/LXN37EiuLYaaqAXT/EQDOTAA4NQHg1ASAUxMATk0AODUB4NQEgFMTAE5NADg1AeDUBIBTEwBOTQA4NQHg1ASAUxMATk0AODUB4NQEgFMTAE5NADg1AeDUBIBTEwBOTQA4NQHg1ASAUxMATk0AOPWTD2kb/rS7eaJtvtnu5hPa5k27mze1zRO7mze1DX/a3XzCBIBTEwBOTQA4NQHg1ASAUxMATk0AODUB4NQEgFM/+XK7m9+gbT5hd/NE2zyxu3mibZ7Y3bxpd/NE27ypbX6D3c1v0DbfbALAqQkApyYAnJoAcGoCwKkJAKcmAJyaAHBqAsCpn/wSbfMJu5tv1jZP7G6eaJsndjef0DbfbHfzG7TNJ+xufoMJAKcmAJyaAHBqAsCpCQCnJgCcmgBwagLAqQkAp37Cf9Lu5k1t86a2eWJ386bdzRNt883a5ondDf89EwBOTQA4NQHg1ASAUxMATk0AODUB4NQEgFMTAE79hP+ktvmE3c0TbfOmtvmEtnlT28AEgFMTAE5NADg1AeDUBIBTEwBOTQA4NQHg1ASAUz/5JXY3f5PdzRNt86a2+YTdzRNtw//e7oZ/bwLAqQkApyYAnJoAcGoCwKkJAKcmAJyaAHBqAsCpn3y5tuFPbfPE7uaJtnlid/NE27ypbZ7Y3TzRNk/sbp5omyd2N0+0zSe0Df97EwBOTQA4NQHg1ASAUxMATk0AODUB4NQEgFMTAE795EN2N/x7u5sn2uYTdjdPtM0Tu5sn2uY32N28aXfD95gAcGoCwKkJAKcmAJyaAHBqAsCpCQCnJgCcmgBw6icf0jZP7G7e1DZ/k93NE23zRNs8sbv5hN3NJ+xu3tQ2b9rdvKltntjdPNE2n7C7+YQJAKcmAJyaAHBqAsCpCQCnJgCcmgBwagLAqQkAp37y5drmTbubN7XNE7ubN7XNE23zxO7mTW3zzdrmTbubJ9rmTbubN7XNE7ubN+1u/iYTAE5NADg1AeDUBIBTEwBOTQA4NQHg1ASAUxMATv3kl9jdfMLu5om2+YTdzZva5ondzRNt86a2+Wa7m2+2u3lT2zyxu/mbTAA4NQHg1ASAUxMATk0AODUB4NQEgFMTAE5NADj1kw/Z3bypbb7Z7uaJtnlT23xC23zC7uaJtvlmbfOm3c1v0DZP7G6+2QSAUxMATk0AODUB4NQEgFMTAE5NADg1AeDUBIBTP/lybfMJu5s3tc032918Qtu8qW3etLt5om3etLv5m7TNm9rmid3NJ0wAODUB4NQEgFMTAE5NADg1AeDUBIBTEwBOTQA49ZMPaZsndjdvapsn2uZNu5sn2uabtc0ntM0Tu5sn2uYTdjdPtM0n7G4+YXfzprb5ZhMATk0AODUB4NQEgFMTAE5NADg1AeDUBIBTEwBO/eRDdjdvapsndjef0Da/we7mTW3zxO7mE9rmTW3zxO7mTW3zprbh35sAcGoCwKkJAKcmAJyaAHBqAsCpCQCnJgCcmgBw6icf0jZP7G6e2N28qW3etLt5om0+oW0+YXfzprZ5YnfzRNs8sbt5U9s8sbv5DdrmTbubbzYB4NQEgFMTAE5NADg1AeDUBIBTEwBOTQA4NQHg1E8+ZHfzprZ5YnfzxO7mTW3zpt3Nm9rmTbubN7XNE7ubJ9qGP7XNE7ubb9Y2T+xuPmECwKkJAKcmAJyaAHBqAsCpCQCnJgCcmgBwagLAqZ98SNu8aXfzRNu8aXfzxO7mibZ5om0+YXfzprb5Zrsb/tQ2b9rdvGl3880mAJyaAHBqAsCpCQCnJgCcmgBwagLAqQkApyYAnPrJX2Z380TbvKltntjdvKltntjdfMLu5k1t88Tu5om2+WZt88Tu5om2eWJ386a2eWJ380TbPLG7+YQJAKcmAJyaAHBqAsCpCQCnJgCcmgBwagLAqQkAp7r/yAe0zSfsbr5Z2/wGu5s3tc0Tu5tPaJtP2N18Qts8sbt5om3etLv5ZhMATk0AODUB4NQEgFMTAE5NADg1AeDUBIBTEwBOdf+RD2ibN+1unmibJ3Y3T7TNm3Y3T7TNE7ubN7XN32R380TbPLG7eaJt+NPu5jeYAHBqAsCpCQCnJgCcmgBwagLAqQkApyYAnJoAcOonH7K7+YTdzZt2N79B2zyxu/mEtnlid/M32d18Qts8sbt5om3+JhMATk0AODUB4NQEgFMTAE5NADg1AeDUBIBTEwBO/eRD2oY/7W5+g7Z5YnfzzdrmN2ibJ3Y3b2ob/jQB4NQEgFMTAE5NADg1AeDUBIBTEwBOTQA4NQHg1E++3O7mN2ibT2ibJ3Y3T7TNN2ubJ3Y3n9A2n7C7+Wa7m7/JBIBTEwBOTQA4NQHg1ASAUxMATk0AODUB4NQEgFM/+SXa5hN2N/xpd/NE27xpd/PNdjdPtM0TbfM3aZsndjffbALAqQkApyYAnJoAcGoCwKkJAKcmAJyaAHBqAsCpn8D/w+7mibZ5YnfDv7e7eaJt3rS7eVPbPLG7+Q0mAJyaAHBqAsCpCQCnJgCcmgBwagLAqQkApyYAnPoJ/0m7mze1zZt2N0+0zRO7myfa5ondzZt2N0+0zRO7myfa5ondzRNt80TbvGl380TbvGl38wkTAE5NADg1AeDUBIBTEwBOTQA4NQHg1ASAUxMATv3kl9jd8Ke2eWJ3883a5ondzRNt86a2eVPb/E3a5ondzRNt880mAJyaAHBqAsCpCQCnJgCcmgBwagLAqQkApyYAnOr+Ix/QNvxpd/NE23zC7uYT2uZNu5sn2uaJ3c0TbfPE7uabtc0Tuxv+NAHg1ASAUxMATk0AODUB4NQEgFMTAE5NADg1AeBU9x8B4MwEgFMTAE5NADg1AeDUBIBTEwBOTQA4NQHg1ASAUxMATk0AODUB4NQEgFMTAE5NADg1AeDUBIBTEwBOTQA4NQHg1ASAUxMATk0AODUB4NQEgFMTAE5NADg1AeDUBIBT/wdnFw8OvzJrygAAAABJRU5ErkJggg=="; - - const actual = await generateQRCode(data, ECC.M, header); - expect(actual).toBe(expected); -}); -test("should return mapped CBOR data for given data with map", () => { - const data = {"name": "Jhon", "id": "207", "l_name": "Honay"}; - const map = {"id": "1", "name": "2", "l_name": "3"}; - const expected = - "a36131633230376132644a686f6e613365486f6e6179"; - - const actual = getMappedData(data, map,true).toString(HEX_ENCODING); - expect(actual).toBe(expected); -}); - -test("should return mapped data for given data with map", () => { - const data = {"name": "Jhon", "id": "207", "l_name": "Honay"}; - const map = {"id": "1", "name": "2", "l_name": "3"}; - const expected ={"2": "Jhon", "1": "207", "3": "Honay"}; - - const actual = getMappedData(data, map); - expect(actual).toStrictEqual(expected); -}); -test("should return mapped CBOR data for given data with map for claim 169 semantics", () => { - - const data = { - "id": "11110000324013", - "version": "1.0", - "language": "EN", - "fullName": "Peter M Jhon", - "firstName": "Peter", - "middleName": "M", - "lastName": "Jhon", - "dob": "19880102", - "gender": "1", - "address": "New City, METRO LINE, PA", - "email": "peter@example.com", - "phone": "+1 234-567", - "nationality": "US", - "maritalStatus": "2", - "guardian": "Jhon Honai", - "binaryImage": - "03CBABDF83D068ACB5DE65B3CDF25E0036F2C546CB90378C587A076E7A759DFD27CA7872B6CDFF339AEAACA61A6023FD1D305A9B4F33CAA248CEDE38B67D7C915C59A51BB4E77D10077A625258873183F82D65F4C482503A5A01F41DEE612C3542E5370987815E592B8EA2020FD3BDDC747897DB10237EAD179E55B441BC6D8BAD07CE535129CF8D559445CC3A29D746FBF1174DE2E7C0F3439BE7DBEA4520CF88825AAE6B1F291A746AB8177C65B2A459DD19BD32C0C3070004B85C1D63034707CC690AB0BA023350C8337FC6894061EB8A714A8F22FE2365E7A904C72DEC9746ABEA1A3296ECACD1A40450794EDCD2B34844E7C19EB7FB1A4AF3B05C3B374BD2941603F72D3F9A62EAB9A2FDAEEEEC8EE6E350F8A1863C0A0AB1B4058D154559A1CD5133EFCF682ABC339960819C9427889D60380B635A7D21D017974BBA57798490F668ADD86DA58125D9C4C1202CA1308F7734E43E8F77CEB0AF968A8F8B88849F9B98B26620399470ED057E7931DED82876DCA896A30D0031A8CBD7B9EDFDF16C15C6853F4F8D9EEC09317C84EDAE4B349FE54D23D8EC7DC9BB9F69FD7B7B23383B64F22E25F", - "binaryImageFormat": "2", - "bestQualityFingers": "[1, 2]", - }; - const map = { - "id": "1", - "version": "2", - "language": "3", - "fullName": "4", - "firstName": "5", - "middleName": "6", - "lastName": "7", - "dob": "8", - "gender": "9", - "address": "10", - "email": "11", - "phone": "12", - "nationality": "13", - "maritalStatus": "14", - "guardian": "15", - "binaryImage": "16", - "binaryImageFormat": "17", - "bestQualityFingers": "18", - }; - - const expected = - "b261316e3131313130303030333234303133613263312e30613362454e61346c5065746572204d204a686f6e61356550657465726136614d6137644a686f6e61386831393838303130326139613162313078184e657720436974792c204d4554524f204c494e452c205041623131717065746572406578616d706c652e636f6d6231326a2b31203233342d35363762313362555362313461326231356a4a686f6e20486f6e61696231367903513033434241424446383344303638414342354445363542334344463235453030333646324335343643423930333738433538374130373645374137353944464432374341373837324236434446463333394145414143413631413630323346443144333035413942344633334341413234384345444533384236374437433931354335394135314242344537374431303037374136323532353838373331383346383244363546344334383235303341354130314634314445453631324333353432453533373039383738313545353932423845413230323046443342444443373437383937444231303233374541443137394535354234343142433644384241443037434535333531323943463844353539343435434333413239443734364642463131373444453245374330463334333942453744424541343532304346383838323541414536423146323931413734364142383137374336354232413435394444313942443332433043333037303030344238354331443633303334373037434336393041423042413032333335304338333337464336383934303631454238413731344138463232464532333635453741393034433732444543393734364142454131413332393645434143443141343034353037393445444344324233343834344537433139454237464231413441463342303543334233373442443239343136303346373244334639413632454142394132464441454545454338454536453335304638413138363343304130414231423430353844313534353539413143443531333345464346363832414243333339393630383139433934323738383944363033383042363335413744323144303137393734424241353737393834393046363638414444383644413538313235443943344331323032434131333038463737333445343345384637374345423041463936384138463842383838343946394239384232363632303339393437304544303537453739333144454438323837364443413839364133304430303331413843424437423945444644463136433135433638353346344638443945454330393331374338344544414534423334394645353444323344384543374443394242394636394644374237423233333833423634463232453235466231376132623138665b312c20325d"; - - const actual = getMappedData(data, map,true).toString(HEX_ENCODING); - expect(actual).toBe(expected); -}); -test("should return properly mapped JSON data for given CBOR", () => { - const expected = {"name": "Jhon", "id": "207", "l_name": "Honay"}; - const map = {"1": "id", "2": "name", "3": "l_name"}; - const data = "a302644a686f6e01633230370365486f6e6179"; - - const actual = decodeMappedData(data, map); - expect(actual).toStrictEqual(expected); -}); - -test("should return properly mapped JSON data for given data", () => { - const expected = {"name": "Jhon", "id": "207", "l_name": "Honay"}; - const map = {"1": "id", "2": "name", "3": "l_name"}; - const data = {"2": "Jhon", "1": "207", "3": "Honay"}; - - const actual = decodeMappedData(data, map); - expect(actual).toStrictEqual(expected); -}); -test("should return properly mapped JSON data for given CBOR for claim 169 semantics", () => { - const expected = { - "id": "11110000324013", - "version": "1.0", - "language": "EN", - "fullName": "Peter M Jhon", - "firstName": "Peter", - "middleName": "M", - "lastName": "Jhon", - "dob": "19880102", - "gender": "1", - "address": "New City, METRO LINE, PA", - "email": "peter@example.com", - "phone": "+1 234-567", - "nationality": "US", - "maritalStatus": "2", - "guardian": "Jhon Honai", - "binaryImage": - "03CBABDF83D068ACB5DE65B3CDF25E0036F2C546CB90378C587A076E7A759DFD27CA7872B6CDFF339AEAACA61A6023FD1D305A9B4F33CAA248CEDE38B67D7C915C59A51BB4E77D10077A625258873183F82D65F4C482503A5A01F41DEE612C3542E5370987815E592B8EA2020FD3BDDC747897DB10237EAD179E55B441BC6D8BAD07CE535129CF8D559445CC3A29D746FBF1174DE2E7C0F3439BE7DBEA4520CF88825AAE6B1F291A746AB8177C65B2A459DD19BD32C0C3070004B85C1D63034707CC690AB0BA023350C8337FC6894061EB8A714A8F22FE2365E7A904C72DEC9746ABEA1A3296ECACD1A40450794EDCD2B34844E7C19EB7FB1A4AF3B05C3B374BD2941603F72D3F9A62EAB9A2FDAEEEEC8EE6E350F8A1863C0A0AB1B4058D154559A1CD5133EFCF682ABC339960819C9427889D60380B635A7D21D017974BBA57798490F668ADD86DA58125D9C4C1202CA1308F7734E43E8F77CEB0AF968A8F8B88849F9B98B26620399470ED057E7931DED82876DCA896A30D0031A8CBD7B9EDFDF16C15C6853F4F8D9EEC09317C84EDAE4B349FE54D23D8EC7DC9BB9F69FD7B7B23383B64F22E25F", - "binaryImageFormat": "2", - "bestQualityFingers": "[1, 2]", - }; - const map = { - "1": "id", - "2": "version", - "3": "language", - "4": "fullName", - "5": "firstName", - "6": "middleName", - "7": "lastName", - "8": "dob", - "9": "gender", - "10": "address", - "11": "email", - "12": "phone", - "13": "nationality", - "14": "maritalStatus", - "15": "guardian", - "16": "binaryImage", - "17": "binaryImageFormat", - "18": "bestQualityFingers", - }; - - const data = - "b261316e3131313130303030333234303133613263312e30613362454e61346c5065746572204d204a686f6e61356550657465726136614d6137644a686f6e61386831393838303130326139613162313078184e657720436974792c204d4554524f204c494e452c205041623131717065746572406578616d706c652e636f6d6231326a2b31203233342d35363762313362555362313461326231356a4a686f6e20486f6e61696231367903513033434241424446383344303638414342354445363542334344463235453030333646324335343643423930333738433538374130373645374137353944464432374341373837324236434446463333394145414143413631413630323346443144333035413942344633334341413234384345444533384236374437433931354335394135314242344537374431303037374136323532353838373331383346383244363546344334383235303341354130314634314445453631324333353432453533373039383738313545353932423845413230323046443342444443373437383937444231303233374541443137394535354234343142433644384241443037434535333531323943463844353539343435434333413239443734364642463131373444453245374330463334333942453744424541343532304346383838323541414536423146323931413734364142383137374336354232413435394444313942443332433043333037303030344238354331443633303334373037434336393041423042413032333335304338333337464336383934303631454238413731344138463232464532333635453741393034433732444543393734364142454131413332393645434143443141343034353037393445444344324233343834344537433139454237464231413441463342303543334233373442443239343136303346373244334639413632454142394132464441454545454338454536453335304638413138363343304130414231423430353844313534353539413143443531333345464346363832414243333339393630383139433934323738383944363033383042363335413744323144303137393734424241353737393834393046363638414444383644413538313235443943344331323032434131333038463737333445343345384637374345423041463936384138463842383838343946394239384232363632303339393437304544303537453739333144454438323837364443413839364133304430303331413843424437423945444644463136433135433638353346344638443945454330393331374338344544414534423334394645353444323344384543374443394242394636394644374237423233333833423634463232453235466231376132623138665b312c20325d"; - - const actual = decodeMappedData(data, map); - expect(actual).toStrictEqual(expected); -}); \ No newline at end of file diff --git a/js/test/UtilsTest.test.js b/js/test/UtilsTest.test.js new file mode 100644 index 0000000..f5e90c7 --- /dev/null +++ b/js/test/UtilsTest.test.js @@ -0,0 +1,333 @@ +const { + decode, + generateQRData, + getMappedData, + decodeMappedData, +} = require("../src"); + +const { + toJson, + replaceKeysAtDepth, + replaceValuesForClaim169, +} = require("../src/utils/cborUtils"); + +const { + toMapWithKeyAndValueMapper, + toListWithKeyAndValueMapper, +} = require("../src/utils/mapperUtils"); + +/* ================================================================== + * TESTS TO COVER REMAINING UNCOVERED LINES + * ================================================================== */ + +/* ------------------------------------------------------------------ + * index.js - Line 69 (decode function - CBOR decode returns falsy) + * ------------------------------------------------------------------ */ + +test("decode returns text when CBOR decode returns null/undefined", () => { + // This covers the case where cbor.decode succeeds but returns falsy value + // We need to trigger the path where decodedCBORData is falsy + const plainText = "plain text without CBOR"; + const encoded = generateQRData(plainText); + const result = decode(encoded); + + expect(result).toBe(plainText); +}); + +/* ------------------------------------------------------------------ + * cborUtils.js - Uncovered Lines + * ------------------------------------------------------------------ */ + +// Line 36, 40 - toJson with Map containing nested Maps +test("toJson handles deeply nested Maps", () => { + const innerMap = new Map([ + ["level2", "value2"], + ["data", new Map([["level3", "value3"]])], + ]); + const outerMap = new Map([ + ["level1", "value1"], + ["nested", innerMap], + ]); + + const result = toJson(outerMap); + + expect(result.level1).toBe("value1"); + expect(result.nested.level2).toBe("value2"); + expect(result.nested.data.level3).toBe("value3"); +}); + +// Line 65 - replaceKeysAtDepth with primitive value (not object/array) +test("replaceKeysAtDepth handles primitive values gracefully", () => { + const primitiveValue = "string value"; + const result = replaceKeysAtDepth(primitiveValue, {}, 0); + expect(result).toBe("string value"); +}); + +test("replaceKeysAtDepth handles number values", () => { + const numberValue = 123; + const result = replaceKeysAtDepth(numberValue, {}, 0); + expect(result).toBe(123); +}); + +// Line 79, 83 - replaceValuesForClaim169 with string format codes and edge cases +test("replaceValuesForClaim169 handles NaN from parseInt", () => { + const input = { + Face: { + "Data format": "invalid-number", + "Data sub format": "0", + Data: "5249", + }, + }; + + const result = replaceValuesForClaim169(input); + + // Should not crash, format should remain unchanged + expect(result.Face["Data format"]).toBe("invalid-number"); +}); + +test("replaceValuesForClaim169 handles missing subformat mapping", () => { + const input = { + Face: { + "Data format": 0, // Valid format (Image) + "Data sub format": 999, // Invalid subformat code + Data: "5249", + }, + }; + + const result = replaceValuesForClaim169(input); + + expect(result.Face["Data format"]).toBe("Image"); + // Subformat should remain unchanged since 999 is not mapped + expect(result.Face["Data sub format"]).toBe(999); +}); + +test("replaceValuesForClaim169 handles null dataFormat value", () => { + const input = { + Face: { + "Data format": null, + "Data sub format": 0, + Data: "5249", + }, + }; + + const result = replaceValuesForClaim169(input); + + // Should not crash, should handle null gracefully + expect(result.Face["Data format"]).toBe(null); +}); + +// Line 119, 129 - replaceValuesForClaim169 biometric handling edge cases +test("replaceValuesForClaim169 handles non-object biometric values", () => { + const input = { + Face: "string instead of object", + Voice: null, + Fingerprint: ["array", "instead", "of", "object"], + }; + + const result = replaceValuesForClaim169(input); + + // Should not crash, should preserve values + expect(result.Face).toBe("string instead of object"); + expect(result.Voice).toBe(null); + expect(Array.isArray(result.Fingerprint)).toBe(true); +}); + +test("replaceValuesForClaim169 handles biometric object missing required keys", () => { + const input = { + Face: { + Data: "5249", + // Missing "Data format" and "Data sub format" + }, + }; + + const result = replaceValuesForClaim169(input); + + // Should not crash, object should remain unchanged + expect(result.Face.Data).toBe("5249"); + expect(result.Face["Data format"]).toBeUndefined(); +}); + +test("replaceValuesForClaim169 handles biometric with only format, no subformat", () => { + const input = { + Face: { + "Data format": 0, + // Missing "Data sub format" + Data: "5249", + }, + }; + + const result = replaceValuesForClaim169(input); + + // Should not crash, should handle missing subformat + expect(result.Face["Data format"]).toBe(0); +}); + +/* ------------------------------------------------------------------ + * mapperUtils.js - Lines 38, 42-47 + * ------------------------------------------------------------------ */ + +// Line 38 - toMapWithKeyAndValueMapper with nested array +test("toMapWithKeyAndValueMapper handles arrays with nested objects", () => { + const obj = { + items: [ + { name: "Item1", value: 10 }, + { name: "Item2", value: 20 }, + ], + }; + const keyMapper = { name: "n", value: "v" }; + + const result = toMapWithKeyAndValueMapper(obj, keyMapper, {}); + + expect(result.items[0].n).toBe("Item1"); + expect(result.items[0].v).toBe(10); + expect(result.items[1].n).toBe("Item2"); + expect(result.items[1].v).toBe(20); +}); + +// Lines 42-47 - toListWithKeyAndValueMapper with nested structures +test("toListWithKeyAndValueMapper handles array with null values", () => { + const arr = [{ name: "A" }, null, { name: "B" }]; + const keyMapper = { name: "n" }; + + const result = toListWithKeyAndValueMapper(arr, keyMapper, {}); + + expect(result[0].n).toBe("A"); + expect(result[1]).toBe(null); + expect(result[2].n).toBe("B"); +}); + +test("toListWithKeyAndValueMapper handles nested arrays of arrays", () => { + const arr = [ + [ + [1, 2], + [3, 4], + ], + [ + [5, 6], + [7, 8], + ], + ]; + + const result = toListWithKeyAndValueMapper(arr, {}, {}); + + expect(result[0][0]).toStrictEqual([1, 2]); + expect(result[0][1]).toStrictEqual([3, 4]); + expect(result[1][0]).toStrictEqual([5, 6]); + expect(result[1][1]).toStrictEqual([7, 8]); +}); + +test("toListWithKeyAndValueMapper handles mixed types in array", () => { + const arr = [{ type: "object" }, "string", 123, true, null, [1, 2, 3]]; + const keyMapper = { type: "t" }; + + const result = toListWithKeyAndValueMapper(arr, keyMapper, {}); + + expect(result[0].t).toBe("object"); + expect(result[1]).toBe("string"); + expect(result[2]).toBe(123); + expect(result[3]).toBe(true); + expect(result[4]).toBe(null); + expect(result[5]).toStrictEqual([1, 2, 3]); +}); + +test("toListWithKeyAndValueMapper handles deeply nested object structures in arrays", () => { + const arr = [ + { + level1: { + level2: { + name: "deep", + }, + }, + }, + ]; + const keyMapper = { name: "n" }; + + const result = toListWithKeyAndValueMapper(arr, keyMapper, {}); + + expect(result[0].level1.level2.n).toBe("deep"); +}); + +/* ------------------------------------------------------------------ + * INTEGRATION TESTS - Covering edge cases through public API + * ------------------------------------------------------------------ */ + +test("getMappedData with arrays containing nulls", () => { + const data = [{ name: "A" }, null, { name: "B" }]; + const keyMapper = { name: "n" }; + + const result = getMappedData(data, keyMapper); + + expect(result[0].n).toBe("A"); + expect(result[1]).toBe(null); + expect(result[2].n).toBe("B"); +}); + +test("decodeMappedData with nested arrays at specific depth", () => { + const input = { + users: [ + { id: 1, name: "A" }, + { id: 2, name: "B" }, + ], + }; + + // The objects inside 'users' array are at depth 1 + const keyMapper = [ + { users: "people" }, // depth 0 - rename users to people + { id: "identifier", name: "label" }, // depth 1 - rename keys inside array objects + ]; + + const result = JSON.parse(decodeMappedData(JSON.stringify(input), keyMapper)); + + expect(result.people[0].identifier).toBe(1); + expect(result.people[0].label).toBe("A"); + expect(result.people[1].identifier).toBe(2); + expect(result.people[1].label).toBe("B"); +}); + +test("replaceKeysAtDepth with arrays at target depth", () => { + const obj = { + data: [{ key: "value1" }, { key: "value2" }], + }; + const mapper = { key: "newKey" }; + + const result = replaceKeysAtDepth(obj, mapper, 1); + + expect(result.data[0].newKey).toBe("value1"); + expect(result.data[1].newKey).toBe("value2"); +}); + +/* ------------------------------------------------------------------ + * EDGE CASES FOR COMPLETE BRANCH COVERAGE + * ------------------------------------------------------------------ */ + +test("toJson handles empty Map", () => { + const emptyMap = new Map(); + const result = toJson(emptyMap); + expect(result).toStrictEqual({}); +}); + +test("toJson handles empty array", () => { + const emptyArray = []; + const result = toJson(emptyArray); + expect(result).toStrictEqual([]); +}); + +test("toJson handles Map with mixed value types", () => { + const map = new Map([ + ["string", "value"], + ["number", 123], + ["boolean", true], + ["null", null], + ["array", [1, 2, 3]], + ["object", { nested: "value" }], + ]); + + const result = toJson(map); + + expect(result.string).toBe("value"); + expect(result.number).toBe(123); + expect(result.boolean).toBe(true); + expect(result.null).toBe(null); + expect(result.array).toStrictEqual([1, 2, 3]); + expect(result.object.nested).toBe("value"); +}); diff --git a/js/test/decode.test.js b/js/test/decode.test.js new file mode 100644 index 0000000..3273103 --- /dev/null +++ b/js/test/decode.test.js @@ -0,0 +1,36 @@ +const { decode, generateQRData } = require("../src"); + +describe("decode", () => { + test("decode simple base45 string", () => { + expect(decode("NCFKVPV0QSIP600GP5L0")).toBe("hello"); + }); + + test("decode CBOR encoded QR data", () => { + expect(decode("NCF3QBXJA5NJRCOC004 QN4")).toBe('{"temp":15}'); + }); + + test("decode CBOR array QR", () => { + const data = '[{"a":1},{"b":2}]'; + const encoded = generateQRData(data); + expect(decode(encoded)).toBe(data); + }); + + test("decode handles null values", () => { + const encoded = generateQRData(JSON.stringify({ key: null })); + expect(decode(encoded)).toBe('{"key":null}'); + }); + + test("decode handles booleans", () => { + const encoded = generateQRData(JSON.stringify({ a: true, b: false })); + expect(JSON.parse(decode(encoded))).toStrictEqual({ a: true, b: false }); + }); + + test("decode returns text when CBOR fails", () => { + const encoded = generateQRData("hello"); + expect(decode(encoded)).toBe("hello"); + }); + + test("decode throws on invalid base45", () => { + expect(() => decode("^1")).toThrow(); + }); +}); diff --git a/js/test/decodeBinary.test.js b/js/test/decodeBinary.test.js new file mode 100644 index 0000000..33895a0 --- /dev/null +++ b/js/test/decodeBinary.test.js @@ -0,0 +1,38 @@ +const { decodeBinary } = require("../src"); +const JSZip = require("jszip"); + +describe("decodeBinary", () => { + test("decode ZIP binary data", async () => { + const zip = new JSZip(); + zip.file("certificate.json", "Hello World!!"); + const data = await zip.generateAsync({ type: "string" }); + + const result = await decodeBinary(new TextEncoder().encode(data)); + expect(result).toBe("Hello World!!"); + }); + + test("decodeBinary throws for unsupported file", async () => { + await expect( + decodeBinary(new TextEncoder().encode("not-zip")) + ).rejects.toThrow("Unsupported binary file type"); + }); + + test("decode ZIP with multiple files", async () => { + const zip = new JSZip(); + zip.file("certificate.json", "Cert"); + zip.file("other.txt", "Other"); + const data = await zip.generateAsync({ type: "string" }); + + const result = await decodeBinary(new TextEncoder().encode(data)); + expect(result).toBe("Cert"); + }); + + test("decode empty ZIP entry", async () => { + const zip = new JSZip(); + zip.file("certificate.json", ""); + const data = await zip.generateAsync({ type: "string" }); + + const result = await decodeBinary(new TextEncoder().encode(data)); + expect(result).toBe(""); + }); +}); diff --git a/js/test/decodeMappedData.test.js b/js/test/decodeMappedData.test.js new file mode 100644 index 0000000..d14312a --- /dev/null +++ b/js/test/decodeMappedData.test.js @@ -0,0 +1,52 @@ +const { decodeMappedData, getMappedData } = require("../src"); + +describe("decodeMappedData", () => { + test("decodeMappedData from CBOR", () => { + const data = "a302644a686f6e01633230370365486f6e6179"; + const map = [{ 1: "id", 2: "name", 3: "l_name" }]; + + const result = JSON.parse(decodeMappedData(data, map)); + expect(result).toStrictEqual({ + name: "Jhon", + id: "207", + l_name: "Honay", + }); + }); + + test("supports array input", () => { + const data = [ + "a302644a686f6e01633230370365486f6e6179", + "a302654a6d69746801633130320363446f65", + ]; + + const map = [{ 1: "id", 2: "name", 3: "l_name" }]; + const result = decodeMappedData(data, map).map(JSON.parse); + + expect(result[1].name).toBe("Jmith"); + }); + + test("depth-based key remapping", () => { + const input = { 1: { 0: "5249", 1: 0, 2: 0 } }; + const mapper = [ + { 1: "Face" }, + { 0: "Data", 1: "Data format", 2: "Data sub format" }, + ]; + + const result = JSON.parse(decodeMappedData(JSON.stringify(input), mapper)); + expect(result.Face["Data sub format"]).toBe("PNG"); + }); + + test("round-trip inverse operation", () => { + const input = { id: "123", name: "Test" }; + const encoded = getMappedData(input, { id: "1", name: "2" }, {}, true); + const decoded = JSON.parse( + decodeMappedData(encoded, [{ 1: "id", 2: "name" }]) + ); + + expect(decoded).toStrictEqual(input); + }); + + test("throws on invalid JSON", () => { + expect(() => decodeMappedData("{bad")).toThrow(); + }); +}); diff --git a/js/test/generateQRCode.test.js b/js/test/generateQRCode.test.js new file mode 100644 index 0000000..8a5e744 --- /dev/null +++ b/js/test/generateQRCode.test.js @@ -0,0 +1,26 @@ +const { generateQRCode } = require("../src"); +const { ECC } = require("../src/types/ECC"); + +describe("generateQRCode", () => { + test("generate base64 QR image", async () => { + const img = await generateQRCode("hello", ECC.M); + expect(img.startsWith("data:image/png;base64")).toBe(true); + }); + + test("generate QR image with header", async () => { + const img = await generateQRCode("hello", ECC.M, "hdr://"); + expect(img.startsWith("data:image/png;base64")).toBe(true); + }); + + test("supports all ECC levels", async () => { + for (const level of [ECC.L, ECC.M, ECC.Q, ECC.H]) { + const img = await generateQRCode("test", level); + expect(img.startsWith("data:image/png;base64")).toBe(true); + } + }); + + test("handles empty string", async () => { + const img = await generateQRCode("", ECC.M); + expect(img.startsWith("data:image/png;base64")).toBe(true); + }); +}); diff --git a/js/test/generateQRData.test.js b/js/test/generateQRData.test.js new file mode 100644 index 0000000..201505f --- /dev/null +++ b/js/test/generateQRData.test.js @@ -0,0 +1,32 @@ +const { generateQRData, decode } = require("../src"); + +describe("generateQRData", () => { + test("encode raw string to QR", () => { + expect(generateQRData("hello")).toBe("NCFKVPV0QSIP600GP5L0"); + }); + + test("encode JSON to CBOR QR", () => { + expect(generateQRData('{"temp":15}')).toBe("NCF3QBXJA5NJRCOC004 QN4"); + }); + + test("encode QR with header", () => { + expect(generateQRData("hello", "hdr://")) + .toBe("hdr://NCFKVPV0QSIP600GP5L0"); + }); + + test("handles empty string", () => { + const result = generateQRData(""); + expect(result.length).toBeGreaterThan(0); + }); + + test("handles complex nested JSON", () => { + const data = JSON.stringify({ a: { b: { c: [1, 2, 3] } } }); + const encoded = generateQRData(data); + expect(decode(encoded)).toBe(data); + }); + + test("handles non-JSON string with header", () => { + const result = generateQRData("plain text", "PREFIX:"); + expect(result.startsWith("PREFIX:")).toBe(true); + }); +}); diff --git a/js/test/getMappedData.test.js b/js/test/getMappedData.test.js new file mode 100644 index 0000000..334e9ce --- /dev/null +++ b/js/test/getMappedData.test.js @@ -0,0 +1,73 @@ +const { getMappedData } = require("../src"); +const { + CLAIM_169_KEY_MAPPER, + CLAIM_169_VALUE_MAPPER, +} = require("../src/shared/Constants"); + +describe("getMappedData", () => { + test("returns mapped object", () => { + const data = { name: "Jhon", id: "207", l_name: "Honay" }; + const map = { id: "1", name: "2", l_name: "3" }; + + expect(getMappedData(data, map)).toStrictEqual({ + 2: "Jhon", + 1: "207", + 3: "Honay", + }); + }); + + test("returns CBOR hex when enabled", () => { + const data = { name: "Jhon", id: "207", l_name: "Honay" }; + const map = { id: "1", name: "2", l_name: "3" }; + + const result = getMappedData(data, map, undefined, true); + expect(typeof result).toBe("string"); + }); + + test("supports array input", () => { + const data = [{ name: "A" }, { name: "B" }]; + const map = { name: "n" }; + + const result = getMappedData(data, map); + expect(result[0].n).toBe("A"); + expect(result[1].n).toBe("B"); + }); + + test("handles nested objects and arrays", () => { + const data = { users: [{ name: "A" }, { name: "B" }] }; + const map = { name: "n" }; + + const result = getMappedData(data, map); + expect(result.users[1].n).toBe("B"); + }); + + test("preserves null values", () => { + const result = getMappedData({ a: null }); + expect(result.a).toBeNull(); + }); + + test("should return CBOR hex string for claim-169 mapped full JSON", () => { + const jsonData = { + "Address": "New House, Near Metro Line, Bengaluru, KA", + "Version": 10, + "Email ID": "janardhan@example.com", + "Full Name": "Janardhan BS", + "Date of Birth": "19840418", + "ID": "3918592438", + "Gender": "Male", + "hello": "world", + "Phone Number": "+919876543210", + "Face": { "Data format": "Image", "Data sub format": "PNG", "Data": "5249" }, + "Voice": { "Data format": "Sound", "Data sub format": "WAV", "Data": "5249" }, + "Nationality": "IN", + }; + const result = getMappedData( + jsonData, + CLAIM_169_KEY_MAPPER, + CLAIM_169_VALUE_MAPPER, + true + ); + expect(typeof result).toBe("string"); + }); + +}); From 76532bd8c9f39f0ff2471d28f716c0bf3de11ebf Mon Sep 17 00:00:00 2001 From: srikanth716 Date: Mon, 22 Dec 2025 16:28:21 +0530 Subject: [PATCH 02/23] [INJIVER-1512] enhance toJson method in js Signed-off-by: srikanth716 --- js/src/index.js | 12 ++++- js/src/utils/cborUtils.js | 15 ++++--- js/test/UtilsTest.test.js | 20 ++++----- js/test/toJson.test.js | 94 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 124 insertions(+), 17 deletions(-) create mode 100644 js/test/toJson.test.js diff --git a/js/src/index.js b/js/src/index.js index 7f4c7c3..48c8707 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -18,12 +18,19 @@ const pako = require("pako"); const cbor = require("cbor-web"); const JSZip = require("jszip"); const { - toJson, + translateToJson, replaceKeysAtDepth, replaceValuesForClaim169, + decodeFromBase64UrlFormat } = require("./utils/cborUtils.js"); const { toMapWithKeyAndValueMapper } = require("./utils/mapperUtils.js"); +function toJson(base64UrlEncodedCborEncodedString) { + const decodedData = decodeFromBase64UrlFormat(base64UrlEncodedCborEncodedString); + const cborDecoded = cbor.decodeFirstSync(decodedData); + return translateToJson(cborDecoded); +} + function generateQRData(data, header = "") { let parsedData = null; let compressedData, b45EncodedData; @@ -123,7 +130,7 @@ function decodeMappedData( try { const bytes = Buffer.from(data, "hex"); const decoded = cbor.decode(bytes); - jsonData = toJson(decoded); + jsonData = translateToJson(decoded); } catch (error) { jsonData = JSON.parse(data); } @@ -140,6 +147,7 @@ function decodeMappedData( } module.exports = { + toJson, generateQRData, generateQRCode, decode, diff --git a/js/src/utils/cborUtils.js b/js/src/utils/cborUtils.js index 89ab080..a9f32ed 100644 --- a/js/src/utils/cborUtils.js +++ b/js/src/utils/cborUtils.js @@ -7,23 +7,23 @@ const { CLAIM_169_ROOT_REVERSE_VALUE_MAPPER, } = require("../shared/Constants"); -function toJson(value) { +function translateToJson(value) { if (value instanceof Map) { const data = {}; value.forEach((mapValue, mapKey) => { - data[mapKey] = toJson(mapValue); + data[mapKey] = translateToJson(mapValue); }); return data; } if (Array.isArray(value)) { - return value.map(toJson); + return value.map(translateToJson); } if (typeof value === "object" && value !== null) { const data = {}; for (const [key, val] of Object.entries(value)) { - data[key] = toJson(val); + data[key] = translateToJson(val); } return data; } @@ -163,8 +163,13 @@ function replaceValuesForClaim169(jsonData) { return result; } +function decodeFromBase64UrlFormat(content) { + return Buffer.from(content, 'base64url'); +} + module.exports = { - toJson, + translateToJson, replaceKeysAtDepth, replaceValuesForClaim169, + decodeFromBase64UrlFormat }; diff --git a/js/test/UtilsTest.test.js b/js/test/UtilsTest.test.js index f5e90c7..171cb5c 100644 --- a/js/test/UtilsTest.test.js +++ b/js/test/UtilsTest.test.js @@ -6,7 +6,7 @@ const { } = require("../src"); const { - toJson, + translateToJson, replaceKeysAtDepth, replaceValuesForClaim169, } = require("../src/utils/cborUtils"); @@ -38,8 +38,8 @@ test("decode returns text when CBOR decode returns null/undefined", () => { * cborUtils.js - Uncovered Lines * ------------------------------------------------------------------ */ -// Line 36, 40 - toJson with Map containing nested Maps -test("toJson handles deeply nested Maps", () => { +// Line 36, 40 - translateToJson with Map containing nested Maps +test("translateToJson handles deeply nested Maps", () => { const innerMap = new Map([ ["level2", "value2"], ["data", new Map([["level3", "value3"]])], @@ -49,7 +49,7 @@ test("toJson handles deeply nested Maps", () => { ["nested", innerMap], ]); - const result = toJson(outerMap); + const result = translateToJson(outerMap); expect(result.level1).toBe("value1"); expect(result.nested.level2).toBe("value2"); @@ -300,19 +300,19 @@ test("replaceKeysAtDepth with arrays at target depth", () => { * EDGE CASES FOR COMPLETE BRANCH COVERAGE * ------------------------------------------------------------------ */ -test("toJson handles empty Map", () => { +test("translateToJson handles empty Map", () => { const emptyMap = new Map(); - const result = toJson(emptyMap); + const result = translateToJson(emptyMap); expect(result).toStrictEqual({}); }); -test("toJson handles empty array", () => { +test("translateToJson handles empty array", () => { const emptyArray = []; - const result = toJson(emptyArray); + const result = translateToJson(emptyArray); expect(result).toStrictEqual([]); }); -test("toJson handles Map with mixed value types", () => { +test("translateToJson handles Map with mixed value types", () => { const map = new Map([ ["string", "value"], ["number", 123], @@ -322,7 +322,7 @@ test("toJson handles Map with mixed value types", () => { ["object", { nested: "value" }], ]); - const result = toJson(map); + const result = translateToJson(map); expect(result.string).toBe("value"); expect(result.number).toBe(123); diff --git a/js/test/toJson.test.js b/js/test/toJson.test.js new file mode 100644 index 0000000..c569761 --- /dev/null +++ b/js/test/toJson.test.js @@ -0,0 +1,94 @@ +const cbor = require("cbor-web"); +const { toJson } = require("../src"); + +describe("toJson (Base64URL CBOR → JSON)", () => { + test("decodes base64url encoded CBOR into JSON object", () => { + // 🔹 Input JSON + const input = { + id: "123", + name: "Alice", + age: 30, + }; + + // 🔹 Encode to CBOR → Base64URL (same as Kotlin flow) + const cborBytes = cbor.encode(input); + const base64Url = Buffer.from(cborBytes).toString("base64url"); + + // 🔹 Decode using toJson + const result = toJson(base64Url); + + expect(result).toStrictEqual(input); + }); + + test("handles nested objects and arrays", () => { + const input = { + user: { + name: "Bob", + roles: ["admin", "user"], + profile: { + active: true, + score: 99, + }, + }, + }; + + const cborBytes = cbor.encode(input); + const base64Url = Buffer.from(cborBytes).toString("base64url"); + + const result = toJson(base64Url); + + expect(result).toStrictEqual(input); + }); + + test("handles CBOR Map correctly", () => { + // 🔹 Explicit Map to ensure translateToJson(Map) path is hit + const map = new Map(); + map.set(1, "one"); + map.set(2, "two"); + + const cborBytes = cbor.encode(map); + const base64Url = Buffer.from(cborBytes).toString("base64url"); + + const result = toJson(base64Url); + + expect(result).toStrictEqual({ + 1: "one", + 2: "two", + }); + }); + + test("handles arrays at root level", () => { + const input = [ + { id: 1, value: "A" }, + { id: 2, value: "B" }, + ]; + + const cborBytes = cbor.encode(input); + const base64Url = Buffer.from(cborBytes).toString("base64url"); + + const result = toJson(base64Url); + + expect(result).toStrictEqual(input); + }); + + test("handles null and primitive values", () => { + const input = { + a: null, + b: true, + c: false, + d: 42, + e: "text", + }; + + const cborBytes = cbor.encode(input); + const base64Url = Buffer.from(cborBytes).toString("base64url"); + + const result = toJson(base64Url); + + expect(result).toStrictEqual(input); + }); + + test("throws error for invalid base64url input", () => { + expect(() => toJson("%%%invalid%%%")).toThrow(); + }); +}); From a50137a5bf2fcf1d42dbeb5621d561e298e6f311 Mon Sep 17 00:00:00 2001 From: srikanth716 Date: Tue, 23 Dec 2025 02:49:22 +0530 Subject: [PATCH 03/23] [INJIVER-1512] add case insensitive mapper Signed-off-by: srikanth716 --- js/src/utils/mapperUtils.js | 39 +++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/js/src/utils/mapperUtils.js b/js/src/utils/mapperUtils.js index 5f2ef11..b261566 100644 --- a/js/src/utils/mapperUtils.js +++ b/js/src/utils/mapperUtils.js @@ -1,20 +1,43 @@ function toMapWithKeyAndValueMapper(data, keyMapper, valueMapper) { - if (data === null || data === undefined || typeof data !== "object") + if (data === null || data === undefined || typeof data !== "object") { return data; + } const result = {}; - for (const [key, value] of Object.entries(data)) { - const mappedKey = keyMapper[key] ?? key; + const normalizedKeyMapper = Object.fromEntries( + Object.entries(keyMapper || {}).map(([sourceKey, mappedKey]) => [ + sourceKey.toLowerCase(), + mappedKey, + ]) + ); + + const normalizedValueMapper = Object.fromEntries( + Object.entries(valueMapper || {}).map(([fieldName, fieldValueMappings]) => [ + fieldName.toLowerCase(), + Object.fromEntries( + Object.entries(fieldValueMappings).map(([sourceValue, mappedValue]) => [ + sourceValue.toLowerCase(), + mappedValue, + ]) + ), + ]) + ); + + for (const [originalKey, originalValue] of Object.entries(data)) { + const normalizedKey = originalKey.toLowerCase(); + const mappedKey = normalizedKeyMapper[normalizedKey] ?? originalKey; + + let processedValue = originalValue; + + const fieldValueMapper = normalizedValueMapper[normalizedKey]; - let processedValue = value; - const fieldValueMapper = valueMapper?.[key]; if ( - value !== null && + originalValue !== null && fieldValueMapper && - fieldValueMapper[value] !== undefined + fieldValueMapper[originalValue.toLowerCase()] !== undefined ) { - processedValue = fieldValueMapper[value]; + processedValue = fieldValueMapper[originalValue.toLowerCase()]; } if (processedValue === null) { From 402a13ebc50d4678808bdf4e80895c2ce662cbea Mon Sep 17 00:00:00 2001 From: srikanth716 Date: Tue, 23 Dec 2025 12:55:32 +0530 Subject: [PATCH 04/23] [INJIVER-1512] add type checks and null checks Signed-off-by: srikanth716 --- js/src/index.js | 4 +-- js/src/utils/cborUtils.js | 5 +++- js/src/utils/mapperUtils.js | 51 +++++++++++++++++++++++-------------- 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/js/src/index.js b/js/src/index.js index 48c8707..0d2adae 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -71,7 +71,7 @@ function decode(data) { const decompressedData = pako.inflate(decodedBase45Data); const textData = new TextDecoder().decode(decompressedData); try { - const decodedCBORData = cbor.decode(decompressedData); + const decodedCBORData = cbor.decodeFirstSync(decompressedData); if (decodedCBORData) return JSON.stringify(decodedCBORData); return textData; } catch (e) { @@ -129,7 +129,7 @@ function decodeMappedData( let jsonData; try { const bytes = Buffer.from(data, "hex"); - const decoded = cbor.decode(bytes); + const decoded = cbor.decodeFirstSync(bytes); jsonData = translateToJson(decoded); } catch (error) { jsonData = JSON.parse(data); diff --git a/js/src/utils/cborUtils.js b/js/src/utils/cborUtils.js index a9f32ed..f9f9c31 100644 --- a/js/src/utils/cborUtils.js +++ b/js/src/utils/cborUtils.js @@ -101,7 +101,8 @@ function replaceValuesForClaim169(jsonData) { CLAIM_169_BIOMETRIC_KEYS.forEach((nestedKey) => { if (result[nestedKey] === undefined) return; - const nestedObject = result[nestedKey]; + const nestedObject = { ...result[nestedKey] }; + if ( typeof nestedObject !== "object" || nestedObject === null || @@ -139,6 +140,8 @@ function replaceValuesForClaim169(jsonData) { if (dataFormatInt === null || Number.isNaN(dataFormatInt)) return; + if (subFormatInt !== null && Number.isNaN(subFormatInt)) return; + const dataFormatValue = CLAIM_169_BIOMETRIC_FORMAT_REVERSE_VALUE_MAPPER[dataFormatInt]; diff --git a/js/src/utils/mapperUtils.js b/js/src/utils/mapperUtils.js index b261566..b61bd2c 100644 --- a/js/src/utils/mapperUtils.js +++ b/js/src/utils/mapperUtils.js @@ -1,28 +1,40 @@ +function normalizeMappers(keyMapper = {}, valueMapper = {}) { + return { + normalizedKeyMapper: Object.fromEntries( + Object.entries(keyMapper).map(([k, v]) => [k.toLowerCase(), v]) + ), + normalizedValueMapper: Object.fromEntries( + Object.entries(valueMapper).map(([field, mappings]) => [ + typeof field === "string" && field.toLowerCase(), + Object.fromEntries( + Object.entries(mappings).map(([src, mapped]) => [ + String(src).toLowerCase(), + mapped, + ]) + ), + ]) + ), + }; +} + function toMapWithKeyAndValueMapper(data, keyMapper, valueMapper) { - if (data === null || data === undefined || typeof data !== "object") { + if (data === null || data === undefined) { + return data; + } + + if (Array.isArray(data)) { + return toListWithKeyAndValueMapper(data, keyMapper, valueMapper); + } + + if (typeof data !== "object") { return data; } const result = {}; + + const normalizedMappers = normalizeMappers(keyMapper, valueMapper); - const normalizedKeyMapper = Object.fromEntries( - Object.entries(keyMapper || {}).map(([sourceKey, mappedKey]) => [ - sourceKey.toLowerCase(), - mappedKey, - ]) - ); - - const normalizedValueMapper = Object.fromEntries( - Object.entries(valueMapper || {}).map(([fieldName, fieldValueMappings]) => [ - fieldName.toLowerCase(), - Object.fromEntries( - Object.entries(fieldValueMappings).map(([sourceValue, mappedValue]) => [ - sourceValue.toLowerCase(), - mappedValue, - ]) - ), - ]) - ); + const { normalizedKeyMapper, normalizedValueMapper } = normalizedMappers; for (const [originalKey, originalValue] of Object.entries(data)) { const normalizedKey = originalKey.toLowerCase(); @@ -34,6 +46,7 @@ function toMapWithKeyAndValueMapper(data, keyMapper, valueMapper) { if ( originalValue !== null && + typeof originalValue === "string" && fieldValueMapper && fieldValueMapper[originalValue.toLowerCase()] !== undefined ) { From dca637c6adf8b3c46ef42791241befd8656a4eca Mon Sep 17 00:00:00 2001 From: srikanth716 Date: Tue, 23 Dec 2025 13:18:09 +0530 Subject: [PATCH 05/23] [INJIVER-1512] add type checks and null checks Signed-off-by: srikanth716 --- js/src/index.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/js/src/index.js b/js/src/index.js index 0d2adae..18a2ee7 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -26,9 +26,13 @@ const { const { toMapWithKeyAndValueMapper } = require("./utils/mapperUtils.js"); function toJson(base64UrlEncodedCborEncodedString) { + if (typeof base64UrlEncodedCborEncodedString !== "string") { + throw new TypeError("Expected base64url-encoded CBOR string"); + } + const decodedData = decodeFromBase64UrlFormat(base64UrlEncodedCborEncodedString); const cborDecoded = cbor.decodeFirstSync(decodedData); - return translateToJson(cborDecoded); + return translateToJson(cborDecoded); } function generateQRData(data, header = "") { @@ -96,17 +100,16 @@ function getMappedData( valueMapper = CLAIM_169_VALUE_MAPPER, cborEnable = false ) { + if (jsonData == null) { + throw new TypeError("jsonData must not be null or undefined"); + } if (Array.isArray(jsonData)) { return jsonData.map((item) => getMappedData(item, keyMapper, valueMapper, cborEnable) ); } - const payload = toMapWithKeyAndValueMapper( - jsonData, - keyMapper, - valueMapper - ); + const payload = toMapWithKeyAndValueMapper(jsonData, keyMapper, valueMapper); if (cborEnable) { return Buffer.from(cbor.encode(payload)).toString("hex"); @@ -120,6 +123,9 @@ function decodeMappedData( keyMapper = CLAIM_169_REVERSE_KEY_MAPPER, valueMapperFunction = replaceValuesForClaim169 ) { + if (data == null) { + throw new TypeError("data must not be null or undefined"); + } if (Array.isArray(data)) { return data.map((item, i) => { return decodeMappedData(item, keyMapper, valueMapperFunction); @@ -135,6 +141,10 @@ function decodeMappedData( jsonData = JSON.parse(data); } + if (!Array.isArray(keyMapper)) { + throw new TypeError("keyMapper must be an array of mapper objects for depth-aware decoding"); + } + keyMapper.forEach((mapper, index) => { jsonData = replaceKeysAtDepth(jsonData, mapper, index); }); From 13f8ea6b50e333f978ac6c19acf170d0de634259 Mon Sep 17 00:00:00 2001 From: srikanth716 Date: Tue, 23 Dec 2025 13:35:57 +0530 Subject: [PATCH 06/23] [INJIVER-1512] enhance cbore decoding Signed-off-by: srikanth716 --- js/src/index.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/js/src/index.js b/js/src/index.js index 18a2ee7..ab0d344 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -29,10 +29,13 @@ function toJson(base64UrlEncodedCborEncodedString) { if (typeof base64UrlEncodedCborEncodedString !== "string") { throw new TypeError("Expected base64url-encoded CBOR string"); } - - const decodedData = decodeFromBase64UrlFormat(base64UrlEncodedCborEncodedString); - const cborDecoded = cbor.decodeFirstSync(decodedData); - return translateToJson(cborDecoded); + try { + const decodedData = decodeFromBase64UrlFormat(base64UrlEncodedCborEncodedString); + const cborDecoded = cbor.decodeFirstSync(decodedData); + return translateToJson(cborDecoded); + } catch (error) { + throw new Error(`Failed to decode CBOR data: ${error.message}`); + } } function generateQRData(data, header = "") { From bd0d55c37c0ac948c7233ee3062a0b094e54e916 Mon Sep 17 00:00:00 2001 From: srikanth716 Date: Wed, 24 Dec 2025 14:41:20 +0530 Subject: [PATCH 07/23] [INJIVER-1512] add cwt data support Signed-off-by: srikanth716 --- js/src/index.js | 7 ++++--- js/test/decode.test.js | 9 +++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/js/src/index.js b/js/src/index.js index ab0d344..b778dc4 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -75,12 +75,13 @@ async function generateQRCode(data, ecc = DEFAULT_ECC_LEVEL, header = "") { function decode(data) { const decodedBase45Data = b45.decode(data); - const decompressedData = pako.inflate(decodedBase45Data); + // Base45 returns number[], convert it + const binaryData = Uint8Array.from(decodedBase45Data); + const decompressedData = pako.inflate(binaryData); const textData = new TextDecoder().decode(decompressedData); try { const decodedCBORData = cbor.decodeFirstSync(decompressedData); - if (decodedCBORData) return JSON.stringify(decodedCBORData); - return textData; + return JSON.stringify(decodedCBORData); } catch (e) { return textData; } diff --git a/js/test/decode.test.js b/js/test/decode.test.js index 3273103..112fa68 100644 --- a/js/test/decode.test.js +++ b/js/test/decode.test.js @@ -33,4 +33,13 @@ describe("decode", () => { test("decode throws on invalid base45", () => { expect(() => decode("^1")).toThrow(); }); + + test("decode cwt data", () => { + const cwtData = + "D28443A10126A1044230315905BFA301781D68747470733A2F2F6170692E72656C65617365642E6D6F7369702E6E65742F2E77656C6C2D6B6E6F776E2F6964612D636F6E74726F6C6C65722E6A736F6E061A676F3D33F518FAA35902F2A36840636F6E7465787483782A68747470733A2F2F7777772E77332E6F72672F323031382F63726564656E7469616C732F7631783F68747470733A2F2F6170692E72656C65617365642E6D6F7369702E6E65742F2E77656C6C2D6B6E6F776E2F6D6F7369702D6964612D636F6E746578742E6A736F6EA163736563781E68747470733A2F2F773369642E6F72672F736563757269747923647479706582784D4D4F53495056657269666961626C6543726564656E7469616C785456657269666961626C6543726564656E7469616C7363726564656E7469616C5375626A656374A86A646174654F6642697274686A313939392F30362F32336A656D61696C6E48756E6465724067616D696C2E636F6D62696478166469643A6A776B3A65794A7264486B694F694A53553045694C434A6C496A6F695156464251694973496E567A5A534936496E4E705A794973496D46735A794936496C4A544D6A55324969776962694936496A4657637A6B3252314E4361336B355345706D64584E6E62474E515745686A656C6C4E3268725A6E5642524769785A545A494E5842444F5739325446686B545563774F5577334C585A6B5A5752555456524454464A4C53565653524F4832324D4452785447684E5158686F567A4E7"; + const qrData = + "NCFO-MEC8KODJR19H2-MM Q98B0338G6J2O7$PT2PK75T1:BYHR3S4PU4OIQC.LVWU+9HL:T:UJ4-9:WTRWVVB2$VQR 66IPA+3DALB2208G%1U-VFJ1NK08-9Q%UB36N2ZUQ*O/QFSOVENQ60WXQP/9F:W9VGVA6O3BV*YNO-AA*R-ZUL%3UXE++3RXSYI5-72+-Q5RREZ6EFP-6DZNPLZF*:FZ6HVUAAOUATKXGL4J6XUL2232FD1BVPYIQ4MVAM.8I69OYARY/5A+QN%B+-9T:QZWV47W+6K-B4L.M58TEF9 M4UCL8V0XHNBHH%0P+BJ6IBRDV.BTE*IPC5Q2ARR158MT:UP2AIXHSTI6L3V8EI0RQ*2$D9FVB2P7SC7TBBY9LG-Q-%LJ3RHRS91F+536SRU042NB YH4QH-W7JAPN.2DEQ7G0BJ9X.B4CO OD5HI4+054Q%NM%DV50D255PSS2J48KOJ29GCBQ73MPC4*P85D0.PNZ5:/79VPK.EGZP3H7/JU69I%Y719H3R5$4V$4U.JJ1NC*GFIX4T:JQOV0HH6XDF4FC.FOV58DTMP389AAH9*PPV+AJ5NKRNZVNM/LLPFW2JA0IZJJS+7LR5%IH4KH.60F2VZTMD.7WQT 1A9H3MGNF 71RC7DCVW5+$P 7TPK8 L40RLRP7+NG1AOW9AKTSE-QPACBGEWT1-9K-CHU%VU.VDVU"; + const decodedData = decode(qrData); + expect(decodedData).toStrictEqual(cwtData); + }); }); From 7433901e8666286ecbfa5271ecb5cf9d83ee427f Mon Sep 17 00:00:00 2001 From: srikanth716 Date: Tue, 6 Jan 2026 00:29:33 +0530 Subject: [PATCH 08/23] [INJIVER-1512] feat: add overloaded methods for enhanced CBOR mapping with backward compatibility Signed-off-by: srikanth716 --- js/src/index.js | 177 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 126 insertions(+), 51 deletions(-) diff --git a/js/src/index.js b/js/src/index.js index b778dc4..44dcd18 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -21,7 +21,7 @@ const { translateToJson, replaceKeysAtDepth, replaceValuesForClaim169, - decodeFromBase64UrlFormat + decodeFromBase64UrlFormat, } = require("./utils/cborUtils.js"); const { toMapWithKeyAndValueMapper } = require("./utils/mapperUtils.js"); @@ -75,13 +75,12 @@ async function generateQRCode(data, ecc = DEFAULT_ECC_LEVEL, header = "") { function decode(data) { const decodedBase45Data = b45.decode(data); - // Base45 returns number[], convert it - const binaryData = Uint8Array.from(decodedBase45Data); - const decompressedData = pako.inflate(binaryData); + const decompressedData = pako.inflate(decodedBase45Data); const textData = new TextDecoder().decode(decompressedData); try { - const decodedCBORData = cbor.decodeFirstSync(decompressedData); - return JSON.stringify(decodedCBORData); + const decodedCBORData = cbor.decode(decompressedData); + if (decodedCBORData) return JSON.stringify(decodedCBORData); + return textData; } catch (e) { return textData; } @@ -98,66 +97,142 @@ async function decodeBinary(data) { } } -function getMappedData( - jsonData, - keyMapper = CLAIM_169_KEY_MAPPER, - valueMapper = CLAIM_169_VALUE_MAPPER, - cborEnable = false -) { - if (jsonData == null) { - throw new TypeError("jsonData must not be null or undefined"); +function translateToJSON(claims, mapper) { + const result = {}; + if (claims instanceof Map) { + claims.forEach((value, param) => { + const key = mapper[param] ? mapper[param] : param; + result[key] = value; + }); + } else if (typeof claims === "object" && claims !== null) { + Object.entries(claims).forEach(([param, value]) => { + const key = mapper[param] ? mapper[param] : param; + result[key] = value; + }); + } else { + throw new Error("Invalid data format for translation"); } - if (Array.isArray(jsonData)) { - return jsonData.map((item) => - getMappedData(item, keyMapper, valueMapper, cborEnable) + return result; +} + +function getMappedData(...args) { + const [jsonData, mapper, cborEnableOrValueMapper, cborEnable] = args; + + const isNewSignature = Array.isArray(mapper) || args.length === 4; + + if (isNewSignature) { + const keyMapper = mapper || CLAIM_169_KEY_MAPPER; + const valueMapper = cborEnableOrValueMapper || CLAIM_169_VALUE_MAPPER; + const cborEnableNew = cborEnable || false; + + if (jsonData == null) { + throw new TypeError("jsonData must not be null or undefined"); + } + if (Array.isArray(jsonData)) { + return jsonData.map((item) => + getMappedData(item, keyMapper, valueMapper, cborEnableNew) + ); + } + + const payload = toMapWithKeyAndValueMapper( + jsonData, + keyMapper, + valueMapper ); - } - const payload = toMapWithKeyAndValueMapper(jsonData, keyMapper, valueMapper); + if (cborEnableNew) { + return Buffer.from(cbor.encode(payload)).toString("hex"); + } - if (cborEnable) { - return Buffer.from(cbor.encode(payload)).toString("hex"); + return payload; } - return payload; -} + const cborEnableOld = cborEnableOrValueMapper || false; -function decodeMappedData( - data, - keyMapper = CLAIM_169_REVERSE_KEY_MAPPER, - valueMapperFunction = replaceValuesForClaim169 -) { - if (data == null) { - throw new TypeError("data must not be null or undefined"); - } - if (Array.isArray(data)) { - return data.map((item, i) => { - return decodeMappedData(item, keyMapper, valueMapperFunction); - }); + if (jsonData === null) { + return null; } - let jsonData; - try { - const bytes = Buffer.from(data, "hex"); - const decoded = cbor.decodeFirstSync(bytes); - jsonData = translateToJson(decoded); - } catch (error) { - jsonData = JSON.parse(data); + if (Array.isArray(jsonData)) { + return jsonData.map((item) => getMappedData(item, mapper, cborEnableOld)); } - if (!Array.isArray(keyMapper)) { - throw new TypeError("keyMapper must be an array of mapper objects for depth-aware decoding"); + const payload = {}; + for (const param in jsonData) { + const key = mapper && mapper[param] ? mapper[param] : param; + const value = jsonData[param]; + + if (value !== null && typeof value === "object") { + payload[key] = getMappedData(value, mapper, false); + } else { + payload[key] = value; + } } - keyMapper.forEach((mapper, index) => { - jsonData = replaceKeysAtDepth(jsonData, mapper, index); - }); + if (cborEnableOld) return cbor.encode(payload); + else return payload; +} + +function decodeMappedData(...args) { + const [data, mapper, valueMapperFunction] = args; + + const isNewSignature = Array.isArray(mapper) || args.length === 3; + + if (isNewSignature) { + const keyMapper = mapper || CLAIM_169_REVERSE_KEY_MAPPER; + const valueMapper = valueMapperFunction || replaceValuesForClaim169; + + if (data == null) { + throw new TypeError("data must not be null or undefined"); + } + if (Array.isArray(data)) { + return data.map((item) => { + return decodeMappedData(item, keyMapper, valueMapper); + }); + } + + let jsonData; + try { + const bytes = Buffer.from(data, "hex"); + const decoded = cbor.decodeFirstSync(bytes); + jsonData = translateToJson(decoded); + } catch (error) { + try { + jsonData = JSON.parse(data); + } catch (parseError) { + throw new Error(`Failed to parse data: ${parseError.message}`); + } + } + + if (!Array.isArray(keyMapper)) { + throw new TypeError( + "keyMapper must be an array of mapper objects for depth-aware decoding" + ); + } + + keyMapper.forEach((mapper, index) => { + jsonData = replaceKeysAtDepth(jsonData, mapper, index); + }); + + if (valueMapper) { + jsonData = valueMapper(jsonData); + } + + return JSON.stringify(jsonData); + } - if (valueMapperFunction) { - jsonData = valueMapperFunction(jsonData); + let jsonData; + try { + jsonData = cbor.decode(data); + } catch (e) { + try { + jsonData = typeof data === "string" ? JSON.parse(data) : data; + } catch (parseError) { + throw new Error(`Failed to decode data: ${parseError.message}`); + } } - return JSON.stringify(jsonData); + return translateToJSON(jsonData, mapper); } module.exports = { @@ -167,5 +242,5 @@ module.exports = { decode, decodeBinary, getMappedData, - decodeMappedData + decodeMappedData, }; From c7928cd9a19e0c0aa1058cab9330d46e64e84e1a Mon Sep 17 00:00:00 2001 From: srikanth716 Date: Tue, 6 Jan 2026 09:44:45 +0530 Subject: [PATCH 09/23] [INJIVER-1512] fix:cbore decoding API Signed-off-by: srikanth716 --- js/src/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/js/src/index.js b/js/src/index.js index 44dcd18..9c153c6 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -30,7 +30,9 @@ function toJson(base64UrlEncodedCborEncodedString) { throw new TypeError("Expected base64url-encoded CBOR string"); } try { - const decodedData = decodeFromBase64UrlFormat(base64UrlEncodedCborEncodedString); + const decodedData = decodeFromBase64UrlFormat( + base64UrlEncodedCborEncodedString + ); const cborDecoded = cbor.decodeFirstSync(decodedData); return translateToJson(cborDecoded); } catch (error) { @@ -223,7 +225,7 @@ function decodeMappedData(...args) { let jsonData; try { - jsonData = cbor.decode(data); + jsonData = cbor.decodeFirstSync(data); } catch (e) { try { jsonData = typeof data === "string" ? JSON.parse(data) : data; From dadd05ffb8657589d4cc5f66558ad8215188948d Mon Sep 17 00:00:00 2001 From: srikanth716 Date: Tue, 6 Jan 2026 10:02:19 +0530 Subject: [PATCH 10/23] [INJIVER-1512] fix:Return type inconsistency between signature paths Signed-off-by: srikanth716 --- js/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/index.js b/js/src/index.js index 9c153c6..9434eaa 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -234,7 +234,7 @@ function decodeMappedData(...args) { } } - return translateToJSON(jsonData, mapper); + return JSON.stringify(translateToJSON(jsonData, mapper)); } module.exports = { From 1cc2b79983f714af4b56de68340093786158a786 Mon Sep 17 00:00:00 2001 From: srikanth716 Date: Tue, 6 Jan 2026 12:16:58 +0530 Subject: [PATCH 11/23] fix: null check for missing file in ZIP archive Signed-off-by: srikanth716 --- js/src/index.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/js/src/index.js b/js/src/index.js index 9434eaa..9ed5963 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -91,9 +91,14 @@ function decode(data) { async function decodeBinary(data) { let decodedData = new TextDecoder("utf-8").decode(data); if (decodedData.startsWith(ZIP_HEADER)) { - return (await JSZip.loadAsync(decodedData)) - .file(DEFAULT_ZIP_FILE_NAME) - .async("text"); + const zip = await JSZip.loadAsync(decodedData); + const file = zip.file(DEFAULT_ZIP_FILE_NAME); + if (!file) { + throw new Error( + `File '${DEFAULT_ZIP_FILE_NAME}' not found in ZIP archive` + ); + } + return file.async("text"); } else { throw new Error("Unsupported binary file type"); } From ba7add5df40920780fbca2e940d5ea780ce304f2 Mon Sep 17 00:00:00 2001 From: srikanth716 Date: Tue, 6 Jan 2026 18:03:11 +0530 Subject: [PATCH 12/23] [INJIVER-1512] add cwt data support Signed-off-by: srikanth716 --- js/src/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/src/index.js b/js/src/index.js index 9ed5963..8346d4b 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -77,7 +77,9 @@ async function generateQRCode(data, ecc = DEFAULT_ECC_LEVEL, header = "") { function decode(data) { const decodedBase45Data = b45.decode(data); - const decompressedData = pako.inflate(decodedBase45Data); + // Base45 returns number[], convert it + const binaryData = Uint8Array.from(decodedBase45Data); + const decompressedData = pako.inflate(binaryData); const textData = new TextDecoder().decode(decompressedData); try { const decodedCBORData = cbor.decode(decompressedData); From fa8b6bacafd7fe273e0d4e2df6b0b33f172c7388 Mon Sep 17 00:00:00 2001 From: srikanth716 Date: Tue, 6 Jan 2026 22:31:02 +0530 Subject: [PATCH 13/23] [INJIVER-1512] fix:implement claim 169 mapper and depricated the old methods Signed-off-by: srikanth716 --- js/src/index.js | 221 +++++++++++++++++++++++++----------------------- 1 file changed, 117 insertions(+), 104 deletions(-) diff --git a/js/src/index.js b/js/src/index.js index 8346d4b..7a789f1 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -106,113 +106,110 @@ async function decodeBinary(data) { } } -function translateToJSON(claims, mapper) { - const result = {}; - if (claims instanceof Map) { - claims.forEach((value, param) => { - const key = mapper[param] ? mapper[param] : param; - result[key] = value; - }); - } else if (typeof claims === "object" && claims !== null) { - Object.entries(claims).forEach(([param, value]) => { - const key = mapper[param] ? mapper[param] : param; - result[key] = value; - }); - } else { - throw new Error("Invalid data format for translation"); +/** + * @deprecated This method is deprecated. Use the new getMappedData with keyMapper and valueMapper parameters instead. + * Maps JSON data using a simple key mapper. + * @param {Object} jsonData - The JSON data to map + * @param {Object} mapper - The key mapper object + * @param {boolean} cborEnable - Whether to encode as CBOR + * @returns {Object|Buffer} Mapped data + */ +function getMappedDataDeprecated(jsonData, mapper, cborEnable = false) { + const payload = {}; + for (const param in jsonData) { + const key = mapper[param] ? mapper[param] : param; + payload[key] = jsonData[param]; } - return result; + if (cborEnable) return cbor.encode(payload); + else return payload; } -function getMappedData(...args) { - const [jsonData, mapper, cborEnableOrValueMapper, cborEnable] = args; - - const isNewSignature = Array.isArray(mapper) || args.length === 4; - - if (isNewSignature) { - const keyMapper = mapper || CLAIM_169_KEY_MAPPER; - const valueMapper = cborEnableOrValueMapper || CLAIM_169_VALUE_MAPPER; - const cborEnableNew = cborEnable || false; - - if (jsonData == null) { - throw new TypeError("jsonData must not be null or undefined"); - } - if (Array.isArray(jsonData)) { - return jsonData.map((item) => - getMappedData(item, keyMapper, valueMapper, cborEnableNew) - ); - } - - const payload = toMapWithKeyAndValueMapper( - jsonData, - keyMapper, - valueMapper - ); - - if (cborEnableNew) { - return Buffer.from(cbor.encode(payload)).toString("hex"); - } - - return payload; - } - - const cborEnableOld = cborEnableOrValueMapper || false; - - if (jsonData === null) { - return null; +/** + * Maps JSON data using key and value mappers with support for arrays. + * @param {Object|Array} jsonData - The JSON data to map + * @param {Object} keyMapper - The key mapper object (default: CLAIM_169_KEY_MAPPER) + * @param {Object|Function} valueMapper - The value mapper object or function (default: CLAIM_169_VALUE_MAPPER) + * @param {boolean} cborEnable - Whether to encode as CBOR and return hex string + * @returns {Object|Array|string|null} Mapped data (hex string if cborEnable is true, null if input is null) + */ +function getMappedData( + jsonData, + keyMapper = CLAIM_169_KEY_MAPPER, + valueMapper = CLAIM_169_VALUE_MAPPER, + cborEnable = false +) { + if (jsonData == null) { + throw new TypeError("jsonData must not be null or undefined"); } if (Array.isArray(jsonData)) { - return jsonData.map((item) => getMappedData(item, mapper, cborEnableOld)); + return jsonData.map((item) => + getMappedData(item, keyMapper, valueMapper, cborEnable) + ); } - const payload = {}; - for (const param in jsonData) { - const key = mapper && mapper[param] ? mapper[param] : param; - const value = jsonData[param]; + const payload = toMapWithKeyAndValueMapper(jsonData, keyMapper, valueMapper); - if (value !== null && typeof value === "object") { - payload[key] = getMappedData(value, mapper, false); - } else { - payload[key] = value; - } + if (cborEnable) { + return Buffer.from(cbor.encode(payload)).toString("hex"); } - if (cborEnableOld) return cbor.encode(payload); - else return payload; + return payload; } -function decodeMappedData(...args) { - const [data, mapper, valueMapperFunction] = args; +/** + * @deprecated This method is deprecated. Use the new decodeMappedData with keyMapper and valueMapper parameters instead. + * Decodes CBOR data and translates using a simple mapper. + * @param {Buffer|Object} data - The data to decode + * @param {Object} mapper - The key mapper object + * @returns {Object} Decoded and translated data + */ +function decodeMappedDataDeprecated(data, mapper) { + try { + const jsonData = cbor.decode(data); + return translateToJSONDeprecated(jsonData, mapper); + } catch (e) { + return translateToJSONDeprecated(data, mapper); + } +} - const isNewSignature = Array.isArray(mapper) || args.length === 3; +/** + * Decodes mapped data with support for depth-aware key mapping and value transformation. + * @param {string|Array} data - The hex-encoded CBOR data or JSON string to decode + * @param {Array} keyMapper - Array of mapper objects for depth-aware decoding (default: CLAIM_169_REVERSE_KEY_MAPPER) + * @param {Function} valueMapper - Function to transform values (default: replaceValuesForClaim169) + * @returns {string|Array} JSON string of decoded and mapped data (or array if input is array) + */ +function decodeMappedData( + data, + keyMapper = CLAIM_169_REVERSE_KEY_MAPPER, + valueMapper = replaceValuesForClaim169 +) { + if (data == null) { + throw new TypeError("data must not be null or undefined"); + } - if (isNewSignature) { - const keyMapper = mapper || CLAIM_169_REVERSE_KEY_MAPPER; - const valueMapper = valueMapperFunction || replaceValuesForClaim169; + if (Array.isArray(data)) { + return data.map((item) => { + return decodeMappedData(item, keyMapper, valueMapper); + }); + } - if (data == null) { - throw new TypeError("data must not be null or undefined"); - } - if (Array.isArray(data)) { - return data.map((item) => { - return decodeMappedData(item, keyMapper, valueMapper); - }); - } + let jsonData; - let jsonData; + try { + const bytes = Buffer.from(data, "hex"); + const decoded = cbor.decodeFirstSync(bytes); + jsonData = translateToJson(decoded); + } catch (error) { try { - const bytes = Buffer.from(data, "hex"); - const decoded = cbor.decodeFirstSync(bytes); - jsonData = translateToJson(decoded); - } catch (error) { - try { - jsonData = JSON.parse(data); - } catch (parseError) { - throw new Error(`Failed to parse data: ${parseError.message}`); - } + jsonData = JSON.parse(data); + } catch (parseError) { + throw new Error(`Failed to decode data: ${error.message}`); } + } + if (keyMapper) { if (!Array.isArray(keyMapper)) { throw new TypeError( "keyMapper must be an array of mapper objects for depth-aware decoding" @@ -220,28 +217,40 @@ function decodeMappedData(...args) { } keyMapper.forEach((mapper, index) => { - jsonData = replaceKeysAtDepth(jsonData, mapper, index); + if (mapper && typeof mapper === "object") { + jsonData = replaceKeysAtDepth(jsonData, mapper, index); + } }); - - if (valueMapper) { - jsonData = valueMapper(jsonData); - } - - return JSON.stringify(jsonData); } - let jsonData; - try { - jsonData = cbor.decodeFirstSync(data); - } catch (e) { - try { - jsonData = typeof data === "string" ? JSON.parse(data) : data; - } catch (parseError) { - throw new Error(`Failed to decode data: ${parseError.message}`); - } + if (valueMapper && typeof valueMapper === "function") { + jsonData = valueMapper(jsonData); } - return JSON.stringify(translateToJSON(jsonData, mapper)); + return JSON.stringify(jsonData); +} + +/** + * @deprecated This method is deprecated. It's kept for backward compatibility. + * Translates claims data using a simple mapper. + * @param {Map|Object} claims - The claims data + * @param {Object} mapper - The key mapper object + * @returns {Object} Translated data + */ +function translateToJSONDeprecated(claims, mapper) { + const result = {}; + if (claims instanceof Map) { + claims.forEach((value, param) => { + const key = mapper[param] ? mapper[param] : param; + result[key] = value; + }); + } else if (typeof claims === "object" && claims !== null) { + Object.entries(claims).forEach(([param, value]) => { + const key = mapper[param] ? mapper[param] : param; + result[key] = value; + }); + } + return result; } module.exports = { @@ -252,4 +261,8 @@ module.exports = { decodeBinary, getMappedData, decodeMappedData, + // Deprecated exports for backward compatibility + getMappedDataDeprecated, + decodeMappedDataDeprecated, + translateToJSONDeprecated, }; From 2ec159a42221f647e2d021846744e8b999ef3cf6 Mon Sep 17 00:00:00 2001 From: srikanth716 Date: Wed, 7 Jan 2026 11:29:38 +0530 Subject: [PATCH 14/23] [INJIVER-1512] fix: removed depricated methods Signed-off-by: srikanth716 --- js/src/index.js | 76 --------------------------------------- js/test/UtilsTest.test.js | 11 +++--- 2 files changed, 5 insertions(+), 82 deletions(-) diff --git a/js/src/index.js b/js/src/index.js index 7a789f1..e54449c 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -106,32 +106,6 @@ async function decodeBinary(data) { } } -/** - * @deprecated This method is deprecated. Use the new getMappedData with keyMapper and valueMapper parameters instead. - * Maps JSON data using a simple key mapper. - * @param {Object} jsonData - The JSON data to map - * @param {Object} mapper - The key mapper object - * @param {boolean} cborEnable - Whether to encode as CBOR - * @returns {Object|Buffer} Mapped data - */ -function getMappedDataDeprecated(jsonData, mapper, cborEnable = false) { - const payload = {}; - for (const param in jsonData) { - const key = mapper[param] ? mapper[param] : param; - payload[key] = jsonData[param]; - } - if (cborEnable) return cbor.encode(payload); - else return payload; -} - -/** - * Maps JSON data using key and value mappers with support for arrays. - * @param {Object|Array} jsonData - The JSON data to map - * @param {Object} keyMapper - The key mapper object (default: CLAIM_169_KEY_MAPPER) - * @param {Object|Function} valueMapper - The value mapper object or function (default: CLAIM_169_VALUE_MAPPER) - * @param {boolean} cborEnable - Whether to encode as CBOR and return hex string - * @returns {Object|Array|string|null} Mapped data (hex string if cborEnable is true, null if input is null) - */ function getMappedData( jsonData, keyMapper = CLAIM_169_KEY_MAPPER, @@ -157,29 +131,6 @@ function getMappedData( return payload; } -/** - * @deprecated This method is deprecated. Use the new decodeMappedData with keyMapper and valueMapper parameters instead. - * Decodes CBOR data and translates using a simple mapper. - * @param {Buffer|Object} data - The data to decode - * @param {Object} mapper - The key mapper object - * @returns {Object} Decoded and translated data - */ -function decodeMappedDataDeprecated(data, mapper) { - try { - const jsonData = cbor.decode(data); - return translateToJSONDeprecated(jsonData, mapper); - } catch (e) { - return translateToJSONDeprecated(data, mapper); - } -} - -/** - * Decodes mapped data with support for depth-aware key mapping and value transformation. - * @param {string|Array} data - The hex-encoded CBOR data or JSON string to decode - * @param {Array} keyMapper - Array of mapper objects for depth-aware decoding (default: CLAIM_169_REVERSE_KEY_MAPPER) - * @param {Function} valueMapper - Function to transform values (default: replaceValuesForClaim169) - * @returns {string|Array} JSON string of decoded and mapped data (or array if input is array) - */ function decodeMappedData( data, keyMapper = CLAIM_169_REVERSE_KEY_MAPPER, @@ -230,29 +181,6 @@ function decodeMappedData( return JSON.stringify(jsonData); } -/** - * @deprecated This method is deprecated. It's kept for backward compatibility. - * Translates claims data using a simple mapper. - * @param {Map|Object} claims - The claims data - * @param {Object} mapper - The key mapper object - * @returns {Object} Translated data - */ -function translateToJSONDeprecated(claims, mapper) { - const result = {}; - if (claims instanceof Map) { - claims.forEach((value, param) => { - const key = mapper[param] ? mapper[param] : param; - result[key] = value; - }); - } else if (typeof claims === "object" && claims !== null) { - Object.entries(claims).forEach(([param, value]) => { - const key = mapper[param] ? mapper[param] : param; - result[key] = value; - }); - } - return result; -} - module.exports = { toJson, generateQRData, @@ -261,8 +189,4 @@ module.exports = { decodeBinary, getMappedData, decodeMappedData, - // Deprecated exports for backward compatibility - getMappedDataDeprecated, - decodeMappedDataDeprecated, - translateToJSONDeprecated, }; diff --git a/js/test/UtilsTest.test.js b/js/test/UtilsTest.test.js index 171cb5c..d7f1186 100644 --- a/js/test/UtilsTest.test.js +++ b/js/test/UtilsTest.test.js @@ -251,15 +251,14 @@ test("toListWithKeyAndValueMapper handles deeply nested object structures in arr * INTEGRATION TESTS - Covering edge cases through public API * ------------------------------------------------------------------ */ -test("getMappedData with arrays containing nulls", () => { +test("getMappedData with arrays containing nulls throws error", () => { const data = [{ name: "A" }, null, { name: "B" }]; const keyMapper = { name: "n" }; - const result = getMappedData(data, keyMapper); - - expect(result[0].n).toBe("A"); - expect(result[1]).toBe(null); - expect(result[2].n).toBe("B"); + expect(() => getMappedData(data, keyMapper)).toThrow(TypeError); + expect(() => getMappedData(data, keyMapper)).toThrow( + "jsonData must not be null or undefined" + ); }); test("decodeMappedData with nested arrays at specific depth", () => { From cb6247a3a54dd78b1fe921e0fb8c2a472ad32c1f Mon Sep 17 00:00:00 2001 From: srikanth716 Date: Wed, 7 Jan 2026 11:37:45 +0530 Subject: [PATCH 15/23] [INJIVER-1512] fix: Error message references wrong exception Signed-off-by: srikanth716 --- js/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/index.js b/js/src/index.js index e54449c..2b61bba 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -156,7 +156,7 @@ function decodeMappedData( try { jsonData = JSON.parse(data); } catch (parseError) { - throw new Error(`Failed to decode data: ${error.message}`); + throw new Error(`Failed to decode data as CBOR or JSON: ${parseError.message}`); } } From 73bec68c47199f5f6191649f7706da90c580859b Mon Sep 17 00:00:00 2001 From: srikanth716 Date: Wed, 7 Jan 2026 12:55:17 +0530 Subject: [PATCH 16/23] [INJIVER-1512] enhance tc and consistency in method Signed-off-by: srikanth716 --- js/src/index.js | 2 +- js/test/decodeMappedData.test.js | 9 ++++++++ js/test/getMappedData.test.js | 39 ++++++++++++++++++++++---------- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/js/src/index.js b/js/src/index.js index 2b61bba..47a16f9 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -82,7 +82,7 @@ function decode(data) { const decompressedData = pako.inflate(binaryData); const textData = new TextDecoder().decode(decompressedData); try { - const decodedCBORData = cbor.decode(decompressedData); + const decodedCBORData = cbor.decodeFirstSync(decompressedData); if (decodedCBORData) return JSON.stringify(decodedCBORData); return textData; } catch (e) { diff --git a/js/test/decodeMappedData.test.js b/js/test/decodeMappedData.test.js index d14312a..4ca177a 100644 --- a/js/test/decodeMappedData.test.js +++ b/js/test/decodeMappedData.test.js @@ -49,4 +49,13 @@ describe("decodeMappedData", () => { test("throws on invalid JSON", () => { expect(() => decodeMappedData("{bad")).toThrow(); }); + + test("decode CBOR hex string for claim-169 mapped full JSON", () => { + const hex = + "ac6249446a333931383539323433386756657273696f6e0a6946756c6c204e616d656c4a616e61726468616e2042536d44617465206f662042697274686a30342d31382d313938346647656e646572644d616c65674164647265737378294e657720486f7573652c204e656172204d6574726f204c696e652c2042656e67616c7572752c204b4168456d61696c204944756a616e61726468616e406578616d706c652e636f6d6c50686f6e65204e756d6265726d2b3931393837363534333231306b4e6174696f6e616c69747962494e6446616365a3644461746164353234396b4461746120666f726d617465496d6167656f446174612073756220666f726d617463504e4765566f696365a3644461746164353234396b4461746120666f726d617465536f756e646f446174612073756220666f726d6174635741566568656c6c6f65776f726c64"; + const decode = decodeMappedData(hex); + const decodedData = JSON.parse(decode); + const hexData = getMappedData(decodedData, [], [], true); + expect(hexData).toBe(hex); + }); }); diff --git a/js/test/getMappedData.test.js b/js/test/getMappedData.test.js index 334e9ce..4d4bf00 100644 --- a/js/test/getMappedData.test.js +++ b/js/test/getMappedData.test.js @@ -1,4 +1,4 @@ -const { getMappedData } = require("../src"); +const { getMappedData, decodeMappedData } = require("../src"); const { CLAIM_169_KEY_MAPPER, CLAIM_169_VALUE_MAPPER, @@ -48,18 +48,18 @@ describe("getMappedData", () => { test("should return CBOR hex string for claim-169 mapped full JSON", () => { const jsonData = { - "Address": "New House, Near Metro Line, Bengaluru, KA", - "Version": 10, + Address: "New House, Near Metro Line, Bengaluru, KA", + Version: 10, "Email ID": "janardhan@example.com", "Full Name": "Janardhan BS", - "Date of Birth": "19840418", - "ID": "3918592438", - "Gender": "Male", - "hello": "world", + "Date of Birth": "04-18-1984", + ID: "3918592438", + Gender: "Male", + hello: "world", "Phone Number": "+919876543210", - "Face": { "Data format": "Image", "Data sub format": "PNG", "Data": "5249" }, - "Voice": { "Data format": "Sound", "Data sub format": "WAV", "Data": "5249" }, - "Nationality": "IN", + Face: { "Data format": "Image", "Data sub format": "PNG", Data: "5249" }, + Voice: { "Data format": "Sound", "Data sub format": "WAV", Data: "5249" }, + Nationality: "IN", }; const result = getMappedData( jsonData, @@ -67,7 +67,22 @@ describe("getMappedData", () => { CLAIM_169_VALUE_MAPPER, true ); - expect(typeof result).toBe("string"); - }); + const decodedString = decodeMappedData(result); + const decoded = JSON.parse(decodedString); + expect(decoded).toMatchObject({ + ID: "3918592438", + Version: 10, + "Full Name": "Janardhan BS", + "Date of Birth": "04-18-1984", + Gender: "Male", + Address: "New House, Near Metro Line, Bengaluru, KA", + "Email ID": "janardhan@example.com", + "Phone Number": "+919876543210", + Nationality: "IN", + Face: { Data: "5249", "Data format": "Image", "Data sub format": "PNG" }, + Voice: { Data: "5249", "Data format": "Sound", "Data sub format": "WAV" }, + hello: "world", + }); + }); }); From 4ecac3bc3ac3a40009ebb4921c0624d60d1e5bb3 Mon Sep 17 00:00:00 2001 From: srikanth716 Date: Wed, 7 Jan 2026 13:32:20 +0530 Subject: [PATCH 17/23] [INJIVER-1512] update readme notifying the breakin changes Signed-off-by: srikanth716 --- js/Readme.md | 159 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 125 insertions(+), 34 deletions(-) diff --git a/js/Readme.md b/js/Readme.md index 8b70b58..ea92ba8 100644 --- a/js/Readme.md +++ b/js/Readme.md @@ -7,10 +7,8 @@ Pixelpass is a library which can do multiple things which are listed below, - Given a JSON String → `generateQRData` → Gives back CBOR encoded data. - Given a CBOR encoded data as byte array → `decode` → Gives back JSON String. - - Given data as byteArray → `decodeBinary` → Gives back JSON String. - -- Given a JSON and Mapper → `getMappedData` → Gives back CBOR encoded data. +- Given a JSON and Mapper → `getMappedData` → Gives back CBOR encoded data or mapped JSON. - Given a CBOR encoded data and Mapper → `decodeMappedData` → Gives back a JSON. @@ -24,15 +22,18 @@ Pixelpass is a library which can do multiple things which are listed below, - When JSON and a Mapper is given, it maps the JSON with Mapper and then does the CBOR encode/decode which further reduces the size of the data. -## Usage +## Usage + `npm i @mosip/pixelpass` [npm](https://www.npmjs.com/package/@mosip/pixelpass) ## Example + Prerequisites -* [nodejs](https://nodejs.org/en/learn/getting-started/how-to-install-nodejs) -* [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) + +- [nodejs](https://nodejs.org/en/learn/getting-started/how-to-install-nodejs) +- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) To run the example app copy the below command and paste it to your terminal. @@ -41,16 +42,17 @@ git clone https://github.com/mosip/pixelpass.git && cd pixelpass && git checkout ``` ## APIs + ### generateQRCode( data, ecc , header ) - - `data` - Data needs to be compressed and encoded. +- `data` - Data needs to be compressed and encoded. - - `ecc` - Error Correction Level for the QR generated. defaults to `"L"`. +- `ecc` - Error Correction Level for the QR generated. defaults to `"L"`. - - `header` - Data header need to be prepend to identify the encoded data. defaults to `""`. +- `header` - Data header need to be prepend to identify the encoded data. defaults to `""`. ```javascript -import { generateQRCode } from '@mosip/pixelpass'; +import { generateQRCode } from "@mosip/pixelpass"; const data = "Hello"; const qrCode = generateQRCode(data, ecc, header); @@ -58,6 +60,7 @@ const qrCode = generateQRCode(data, ecc, header); // ecc is Error Correction Level for the QR generated. defaults to "L". // header defaults to empty string if not passed. ``` + The `generateQRCode` takes a data, ECC (Error correction level) which when not passed defaults to L and header which defaults to empty string if not passed. Returns a base64 encoded PNG image. @@ -68,30 +71,32 @@ Returns a base64 encoded PNG image. - `header` - Data header need to be prepend to identify the encoded data. defaults to `""`. ```javascript -import { generateQRData } from '@mosip/pixelpass'; +import { generateQRData } from "@mosip/pixelpass"; -const jsonString = "{\"name\":\"Steve\",\"id\":\"1\",\"l_name\":\"jobs\"}"; +const jsonString = '{"name":"Steve","id":"1","l_name":"jobs"}'; const header = "jsonstring"; const encodedCBORData = generateQRData(jsonString, header); // header defaults to empty string if not passed. ``` + The `generateQRData` takes a valid JSON string and a header which when not passed defaults to an empty string. This API will return a base45 encoded string which is `Compressed > CBOR Encoded > Base45 Encoded`. - ### decode( data ) - `data` - Data needs to be decoded and decompressed without header. ```javascript -import { decode } from '@mosip/pixelpass'; +import { decode } from "@mosip/pixelpass"; -const b45EncodedData = "NCFWTL$PPB$PN$AWGAE%5UW5A%ADFAHR9 IE:GG6ZJJCL2.AJKAMHA100+8S.1"; +const b45EncodedData = + "NCFWTL$PPB$PN$AWGAE%5UW5A%ADFAHR9 IE:GG6ZJJCL2.AJKAMHA100+8S.1"; const jsonString = decode(b45EncodedData); ``` -The `decode` will take a `string` as parameter and gives us decoded JSON string which is Base45 `Decoded > CBOR Decoded > Decompressed`. + +The `decode` will take a `string` as parameter and gives us decoded JSON string which is Base45 `Decoded > CBOR Decoded > Decompressed`. ### decodeBinary( data ) @@ -103,52 +108,137 @@ import { decodeBinary } from '@mosip/pixelpass'; const zipdata = ; const decompressedData = decodeBinary(zipdata); ``` -The `decodeBinary` will take a `UInt8ByteArray` as parameter and gives us unzipped string. Currently only zip binary data is only supported. +The `decodeBinary` will take a `UInt8ByteArray` as parameter and gives us unzipped string. Currently only zip binary data is only supported. -### getMappedData( jsonData, mapper, cborEnable ); +### getMappedData( jsonData, keyMapper, valueMapper, cborEnable ) -- `jsonData` - A JSON data. -- `mapper` - A Map which is used to map with the JSON. +- `jsonData` - A JSON data or an array of JSON data. +- `keyMapper` - A Map which is used to map the keys of the JSON. +- `valueMapper` - A Map which is used to map the values of the JSON. - `cborEnable` - A Boolean which is used to enable or disable CBOR encoding on mapped data. Defaults to `false` if not provided. ```javascript -import { getMappedData } from '@mosip/pixelpass'; +import { getMappedData } from "@mosip/pixelpass"; -const jsonData = {"name": "Jhon", "id": "207", "l_name": "Honay"}; -const mapper = {"id": "1", "name": "2", "l_name": "3"}; +const jsonData = { name: "Jhon", id: "207", l_name: "Honay" }; +const mapper = { id: "1", name: "2", l_name: "3" }; +const valueMapper = {}; // Optional value mapping -const byteBuffer = getMappedData(jsonData, mapper,true); +const result = getMappedData(jsonData, keyMapper, valueMapper, true); -const cborEncodedString = byteBuffer.toString('hex'); +// If cborEnable is true, result is a hex string of CBOR encoded data +const cborEncodedString = result; + +// If cborEnable is false, result is the mapped JSON object +const mappedJson = getMappedData(jsonData, keyMapper, valueMapper, false); ``` -The `getMappedData` takes 3 arguments a JSON and a map with which we will be creating a new map with keys and values mapped based on the mapper. The third parameter is an optional value to enable or disable CBOR encoding on the mapped data. + +The `getMappedData` takes 4 arguments: + +1. A JSON object or array of JSON objects +2. A key mapper to map JSON keys +3. A value mapper to map JSON values (can be empty object if no value mapping needed) +4. An optional boolean to enable or disable CBOR encoding on the mapped data (defaults to `false`) + The example of a converted map would look like, `{ "1": "207", "2": "Jhon", "3": "Honay"}` -### decodeMappedData( data, mapper ) +When `cborEnable` is `true`, the function returns a hex string of the CBOR encoded mapped data. +When `cborEnable` is `false`, the function returns the mapped JSON object directly. + +**⚠️ DEPRECATION NOTICE**: The previous 3-argument signature `getMappedData(jsonData, mapper, cborEnable)` is deprecated. Please use the new 4-argument signature with separate `keyMapper` and `valueMapper` parameters. -- `data` - A CBOREncoded string or a mapped JSON. -- `mapper` - A Map which is used to map with the JSON. +### decodeMappedData( data, keyMapper, valueMapper ) + +- `data` - A CBOR encoded hex string, a mapped JSON string, or an array of either. +- `keyMapper` - An array of mapper objects for depth-aware key decoding. Each mapper object handles keys at a specific depth level. +- `valueMapper` - A function to transform values in the decoded JSON. Optional. ```javascript -import { decodeMappedData } from '@mosip/pixelpass'; +import { decodeMappedData } from "@mosip/pixelpass"; const cborEncodedString = "a302644a686f6e01633230370365486f6e6179"; -const mapper = {"1": "id", "2": "name", "3": "l_name"}; +const keyMapper = [ + { 1: "id", 2: "name", 3: "l_name" }, // Mapper for depth 0 +]; +const valueMapper = (jsonData) => { + // Optional: transform values if needed + return jsonData; +}; + +const jsonString = decodeMappedData(cborEncodedString, keyMapper, valueMapper); +const jsonData = JSON.parse(jsonString); +``` + +The `decodeMappedData` takes 3 arguments: + +1. A CBOR encoded hex string, a JSON string, or an array of either +2. An array of mapper objects for depth-aware key decoding (each index corresponds to a depth level) +3. An optional value mapper function to transform the decoded data + +The function will: +- First attempt to decode the data as CBOR (if it's a hex string) +- If CBOR decoding fails, it will try to parse it as JSON +- Apply key mapping at each depth level using the provided keyMapper array +- Apply value transformation using the valueMapper function (if provided) +- Return a JSON string representation of the decoded and mapped data + +Example with nested objects: + +```javascript +const keyMapper = [ + { 1: "id", 2: "name" }, // Maps keys at depth 0 + { a: "street", b: "city" }, // Maps keys at depth 1 (nested objects) +]; + +const result = decodeMappedData(cborEncodedData, keyMapper); +``` + +The function also supports arrays of data: + +```javascript +const dataArray = [encodedData1, encodedData2, encodedData3]; +const results = decodeMappedData(dataArray, keyMapper, valueMapper); +// Returns an array of decoded JSON strings +``` + +**⚠️ BREAKING CHANGE**: The signature has been updated from the previous 2-argument version `decodeMappedData(data, mapper)` to the new 3-argument version `decodeMappedData(data, keyMapper, valueMapper)`. The `keyMapper` is now expected to be an array of mapper objects for depth-aware decoding, and an optional `valueMapper` function can be provided for value transformations. + +#### Migration Guide from Old to New API + +**Old API (Deprecated):** + +```javascript +const mapper = { 1: "id", 2: "name", 3: "l_name" }; const jsonData = decodeMappedData(cborEncodedString, mapper); ``` -The `decodeMappedData` takes 2 arguments a string which is CBOR Encoded or a mapped JSON and a map with which we will be creating a JSON by mapping the keys and values. If the data provided is CBOR encoded string the API will do a CBOR decode first ad then proceed with re-mapping the data. -The example of the returned JSON would look like, `{"name": "Jhon", "id": "207", "l_name": "Honay"}` +**New API:** +```javascript +const keyMapper = [ + { 1: "id", 2: "name", 3: "l_name" }, // Wrap mapper in array +]; +const valueMapper = null; // or a transformation function +const jsonString = decodeMappedData(cborEncodedString, keyMapper, valueMapper); +const jsonData = JSON.parse(jsonString); // Parse the returned JSON string +``` + +Key differences: + +- `keyMapper` must now be an array (for depth-aware mapping) +- `valueMapper` is a new optional parameter for value transformations +- Returns a JSON string instead of an object (needs `JSON.parse()`) +- Supports arrays of data for batch processing ## Errors / Exceptions + - `Cannot read properties of null (reading 'length')` - thrown when the string passed to encode is null. - `Cannot read properties of undefined (reading 'length')` - thrown when the string passed to encode is undefined. -- `byteArrayArg is null or undefined.` - thrown when the string passed to encode is null or undefined. +- `byteArrayArg is null or undefined.` - thrown when the string passed to encode is null or undefined. - `utf8StringArg is null or undefined.` - thrown when the string passed to decode is null or undefined. @@ -158,6 +248,7 @@ The example of the returned JSON would look like, `{"name": "Jhon", "id": "207", - `incorrect data check` - thrown when the string passed to decode is invalid. +- `jsonData must not be null or undefined` - thrown when null or undefined is passed to `getMappedData`. ## License MPL-2.0 From 0c4d1d3ef4f59f55bc27b1df93008793ee1a0673 Mon Sep 17 00:00:00 2001 From: srikanth716 Date: Wed, 7 Jan 2026 13:38:21 +0530 Subject: [PATCH 18/23] [INJIVER-1512] update readme notifying the breakin changes Signed-off-by: srikanth716 --- js/Readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/Readme.md b/js/Readme.md index ea92ba8..de0521b 100644 --- a/js/Readme.md +++ b/js/Readme.md @@ -143,14 +143,14 @@ The `getMappedData` takes 4 arguments: The example of a converted map would look like, `{ "1": "207", "2": "Jhon", "3": "Honay"}` -When `cborEnable` is `true`, the function returns a hex string of the CBOR encoded mapped data. +When `cborEnable` is `true`, the function returns a hex string of the CBOR-encoded mapped data. When `cborEnable` is `false`, the function returns the mapped JSON object directly. **⚠️ DEPRECATION NOTICE**: The previous 3-argument signature `getMappedData(jsonData, mapper, cborEnable)` is deprecated. Please use the new 4-argument signature with separate `keyMapper` and `valueMapper` parameters. ### decodeMappedData( data, keyMapper, valueMapper ) -- `data` - A CBOR encoded hex string, a mapped JSON string, or an array of either. +- `data` - A CBOR-encoded hex string, a mapped JSON string, or an array of either. - `keyMapper` - An array of mapper objects for depth-aware key decoding. Each mapper object handles keys at a specific depth level. - `valueMapper` - A function to transform values in the decoded JSON. Optional. @@ -172,7 +172,7 @@ const jsonData = JSON.parse(jsonString); The `decodeMappedData` takes 3 arguments: -1. A CBOR encoded hex string, a JSON string, or an array of either +1. A CBOR-encoded hex string, a JSON string, or an array of either 2. An array of mapper objects for depth-aware key decoding (each index corresponds to a depth level) 3. An optional value mapper function to transform the decoded data From 6368f2ed323c69d25418a12a1e0ac0fc72baad0b Mon Sep 17 00:00:00 2001 From: srikanth716 Date: Wed, 7 Jan 2026 14:11:15 +0530 Subject: [PATCH 19/23] [INJIVER-1512] update readme notifying the breakin changes Signed-off-by: srikanth716 --- js/Readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/Readme.md b/js/Readme.md index de0521b..5de6ef9 100644 --- a/js/Readme.md +++ b/js/Readme.md @@ -4,11 +4,11 @@ Pixelpass is a library which can do multiple things which are listed below, - Given a data → `generateQRCode` → returns a QR Code. -- Given a JSON String → `generateQRData` → Gives back CBOR encoded data. +- Given a JSON String → `generateQRData` → Gives back CBOR-encoded data. - Given a CBOR encoded data as byte array → `decode` → Gives back JSON String. - Given data as byteArray → `decodeBinary` → Gives back JSON String. -- Given a JSON and Mapper → `getMappedData` → Gives back CBOR encoded data or mapped JSON. +- Given a JSON and Mapper → `getMappedData` → Gives back CBOR-encoded data or mapped JSON. - Given a CBOR encoded data and Mapper → `decodeMappedData` → Gives back a JSON. @@ -109,7 +109,7 @@ const zipdata = ; const decompressedData = decodeBinary(zipdata); ``` -The `decodeBinary` will take a `UInt8ByteArray` as parameter and gives us unzipped string. Currently only zip binary data is only supported. +The `decodeBinary` will take a `UInt8ByteArray` as parameter and gives us unzipped string. Currently only zip binary data is supported. ### getMappedData( jsonData, keyMapper, valueMapper, cborEnable ) From 9085d6e6e7721b8518ae5b92d2d95c13fe126a18 Mon Sep 17 00:00:00 2001 From: srikanth716 Date: Wed, 7 Jan 2026 14:29:12 +0530 Subject: [PATCH 20/23] [INJIVER-1512] update readme notifying the breakin changes Signed-off-by: srikanth716 --- js/Readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/Readme.md b/js/Readme.md index 5de6ef9..9467463 100644 --- a/js/Readme.md +++ b/js/Readme.md @@ -6,11 +6,11 @@ Pixelpass is a library which can do multiple things which are listed below, - Given a JSON String → `generateQRData` → Gives back CBOR-encoded data. -- Given a CBOR encoded data as byte array → `decode` → Gives back JSON String. +- Given a CBOR-encoded data as byte array → `decode` → Gives back JSON String. - Given data as byteArray → `decodeBinary` → Gives back JSON String. - Given a JSON and Mapper → `getMappedData` → Gives back CBOR-encoded data or mapped JSON. -- Given a CBOR encoded data and Mapper → `decodeMappedData` → Gives back a JSON. +- Given a CBOR-encoded data and Mapper → `decodeMappedData` → Gives back a JSON. ## Features @@ -127,7 +127,7 @@ const valueMapper = {}; // Optional value mapping const result = getMappedData(jsonData, keyMapper, valueMapper, true); -// If cborEnable is true, result is a hex string of CBOR encoded data +// If cborEnable is true, result is a hex string of CBOR-encoded data const cborEncodedString = result; // If cborEnable is false, result is the mapped JSON object From be6c8e0c71413327618c21b91f5506c657776ae5 Mon Sep 17 00:00:00 2001 From: srikanth716 Date: Wed, 7 Jan 2026 15:04:44 +0530 Subject: [PATCH 21/23] [INJIVER-1512] update readme notifying the breakin changes Signed-off-by: srikanth716 --- js/Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/Readme.md b/js/Readme.md index 9467463..c5e9119 100644 --- a/js/Readme.md +++ b/js/Readme.md @@ -122,7 +122,7 @@ The `decodeBinary` will take a `UInt8ByteArray` as parameter and gives us unzipp import { getMappedData } from "@mosip/pixelpass"; const jsonData = { name: "Jhon", id: "207", l_name: "Honay" }; -const mapper = { id: "1", name: "2", l_name: "3" }; +const keyMapper = { id: "1", name: "2", l_name: "3" }; const valueMapper = {}; // Optional value mapping const result = getMappedData(jsonData, keyMapper, valueMapper, true); From 5299f026113228ed8d8c70f648aecd06fe2de63b Mon Sep 17 00:00:00 2001 From: srikanth716 Date: Wed, 7 Jan 2026 18:27:48 +0530 Subject: [PATCH 22/23] [MOSIP-44197] fix: organizationUrl of the repo Signed-off-by: srikanth716 --- js/package.json | 4 ++-- kotlin/PixelPass/publish-artifact.gradle | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/js/package.json b/js/package.json index 2376f64..3e284c3 100644 --- a/js/package.json +++ b/js/package.json @@ -1,10 +1,10 @@ { "name": "@mosip/pixelpass", "version": "0.8.0", - "repository": "https://github.com/mosip/pixelpass", + "repository": "https://github.com/inji/pixelpass", "author": "MOSIP", "license": "MPL-2.0", - "homepage": "https://github.com/mosip/pixelpass", + "homepage": "https://github.com/inji/pixelpass", "description": "JS module that can be used to compress (zlib) , encode (base45) and generate QR out of given any data. Also can be used to do the operation vice versa", "main": "src/index.js", "scripts": { diff --git a/kotlin/PixelPass/publish-artifact.gradle b/kotlin/PixelPass/publish-artifact.gradle index 34e7690..df78f10 100644 --- a/kotlin/PixelPass/publish-artifact.gradle +++ b/kotlin/PixelPass/publish-artifact.gradle @@ -29,7 +29,7 @@ publishing { pom { withXml { asNode().appendNode('name', "Pixelpass") - asNode().appendNode('url', "https://github.com/mosip/pixelpass") + asNode().appendNode('url', "https://github.com/inji/pixelpass") asNode().appendNode('description', "Kotlin library to generate QR code from VC and decode the data") asNode().appendNode('licenses').appendNode('license').with { @@ -38,9 +38,9 @@ publishing { } asNode().appendNode('scm').with { - appendNode('connection', 'scm:git:git://github.com/mosip/pixelpass.git') - appendNode('developerConnection', 'scm:git:ssh://github.com:mosip/pixelpass.git') - appendNode('url', "https://github.com/mosip/pixelpass") + appendNode('connection', 'scm:git:git://github.com/inji/pixelpass.git') + appendNode('developerConnection', 'scm:git:ssh://github.com:inji/pixelpass.git') + appendNode('url', "https://github.com/inji/pixelpass") appendNode('tag', "HEAD") } @@ -49,7 +49,7 @@ publishing { appendNode('name', 'Mosip') appendNode('email', 'mosip.emailnotifier@gmail.com') appendNode('organization', 'io.mosip') - appendNode("organizationUrl", "https://github.com/mosip/pixelpass") + appendNode("organizationUrl", "https://github.com/inji/pixelpass") } def dependenciesNode = asNode().appendNode('dependencies') @@ -123,7 +123,7 @@ publishing { pom { withXml { asNode().appendNode('name', "Pixelpass") - asNode().appendNode('url', "https://github.com/mosip/pixelpass") + asNode().appendNode('url', "https://github.com/inji/pixelpass") asNode().appendNode('description', "Java library to generate QR code from VC and decode the data") asNode().appendNode('licenses').appendNode('license').with { @@ -136,7 +136,7 @@ publishing { appendNode('name', 'Mosip') appendNode('email', 'mosip.emailnotifier@gmail.com') appendNode('organization', 'io.mosip') - appendNode('organizationUrl', 'https://github.com/mosip/pixelpass') + appendNode('organizationUrl', 'https://github.com/inji/pixelpass') } def dependenciesNode = asNode().appendNode('dependencies') @@ -158,9 +158,9 @@ publishing { pluginNode.appendNode('version', '3.0.1') asNode().appendNode('scm').with { - appendNode('connection', 'scm:git:git://github.com/mosip/pixelpass.git') - appendNode('developerConnection', 'scm:git:ssh://github.com:mosip/pixelpass.git') - appendNode('url', "https://github.com/mosip/pixelpass") + appendNode('connection', 'scm:git:git://github.com/inji/pixelpass.git') + appendNode('developerConnection', 'scm:git:ssh://github.com:inji/pixelpass.git') + appendNode('url', "https://github.com/inji/pixelpass") appendNode('tag', "HEAD") } From 5fd58966eed586d5fa31eceec31e2b6c763c3a6d Mon Sep 17 00:00:00 2001 From: srikanth716 Date: Thu, 8 Jan 2026 19:44:25 +0530 Subject: [PATCH 23/23] [INJIVER-1512] update readme Signed-off-by: srikanth716 --- js/Readme.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/js/Readme.md b/js/Readme.md index c5e9119..d3fadac 100644 --- a/js/Readme.md +++ b/js/Readme.md @@ -12,6 +12,35 @@ Pixelpass is a library which can do multiple things which are listed below, - Given a CBOR-encoded data and Mapper → `decodeMappedData` → Gives back a JSON. +## 🚨 Breaking Changes (`0.8.0` and later) + +Starting from version `0.8.0`, the following APIs have undergone changes +to support advanced key/value compression and depth-aware decoding. + +These updates improve interoperability and reduce CBOR payload sizes. + +## 🔄 API Contract Changes + +### 1. `getMappedData` --- Signature Change + +#### Old (Deprecated) + +`getMappedData(jsonData, mapper, cborEnable?)` + +#### New (Recommended) + +`getMappedData(jsonData, keyMapper, valueMapper, cborEnable?)` + +### 2. `decodeMappedData` --- Signature Change + +#### Old (Deprecated) + +`decodeMappedData(data, mapper)` + +#### New (Recommended) + +`decodeMappedData(data, keyMapper, valueMapperFunction?)`. + ## Features - Compresses the data using zlib compression of level 9.