UNPKG

@animo-id/expo-digital-credentials-api

Version:
120 lines 4.98 kB
import { decodeBase64, encodeBase64 } from './util'; function recursivelyMapSdJwtDc(claims, display, path) { const result = {}; for (const [key, value] of Object.entries(claims)) { if (!value || Array.isArray(value) || typeof value !== 'object') { const claimDisplay = display.claims?.find((claim) => claim.path.join('.') === [...path, key].join('.')); const displayName = claimDisplay?.displayName ?? key; result[key] = { display: displayName, // Do not allow matching based on array claims for now value: Array.isArray(value) ? undefined : value ?? undefined, }; } else { result[key] = recursivelyMapSdJwtDc(value, display, [...path, key]); } } return result; } // TODO: we should allow registering a custom matcher and thus custom credential bytes structure export function getEncodedCredentialsBase64(items, { debug }) { const textEncoder = new TextEncoder(); const chunks = []; // Create icon map const iconRecord = {}; for (const item of items) { const iconBytes = item.display.iconDataUrl ? decodeBase64(item.display.iconDataUrl.replace('data:image/png;base64,', '').replace('data:image/jpg;base64,', '')) : new Uint8Array(0); iconRecord[item.id] = { iconValue: iconBytes, iconOffset: 0 }; } // Calculate total icon size const totalIconSize = Object.values(iconRecord).reduce((sum, icon) => sum + icon.iconValue.length, 0); // Calculate and write JSON offset (4 bytes, little endian) const jsonOffset = 4 + totalIconSize; const offsetBuffer = new ArrayBuffer(4); new DataView(offsetBuffer).setInt32(0, jsonOffset, true); chunks.push(new Uint8Array(offsetBuffer)); // Write icons and update offsets let currentOffset = 4; for (const icon of Object.values(iconRecord)) { icon.iconOffset = currentOffset; chunks.push(icon.iconValue); currentOffset += icon.iconValue.length; } // Create credential JSON structure // Mapping of doctype/vct => credentials[] const mdocCredentials = {}; const sdJwtCredentials = {}; for (const item of items) { const icon = iconRecord[item.id]; const credentialJson = { id: item.id, title: item.display.title, subtitle: item.display.subtitle, icon: icon.iconValue.length > 0 ? { start: iconRecord[item.id].iconOffset, length: iconRecord[item.id].iconValue.length, } : null, }; if (item.credential.format === 'mso_mdoc') { const pathsJson = {}; for (const [namespace, elements] of Object.entries(item.credential.namespaces)) { pathsJson[namespace] = {}; for (const [element, value] of Object.entries(elements)) { const claimDisplay = item.display.claims?.find((claim) => claim.path.join('.') === [namespace, element].join('.')); const displayName = claimDisplay?.displayName ?? element; pathsJson[namespace][element] = { value: value ?? undefined, display: displayName, }; } } // Add to doctype array if (!mdocCredentials[item.credential.doctype]) { mdocCredentials[item.credential.doctype] = []; } mdocCredentials[item.credential.doctype].push({ ...credentialJson, paths: pathsJson, }); } else if (item.credential.format === 'dc+sd-jwt') { const pathsJson = recursivelyMapSdJwtDc(item.credential.claims, item.display, []); // Add to vct array if (!sdJwtCredentials[item.credential.vct]) { sdJwtCredentials[item.credential.vct] = []; } sdJwtCredentials[item.credential.vct].push({ ...credentialJson, paths: pathsJson, }); } else { throw new Error('Unsupported format. Only mso_mdoc supported'); } } // Create final JSON structure const registryJson = { debug, credentials: { mso_mdoc: mdocCredentials, 'dc+sd-jwt': sdJwtCredentials, }, }; // Convert JSON to bytes and add to chunks chunks.push(textEncoder.encode(JSON.stringify(registryJson))); // Combine all chunks const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0); const result = new Uint8Array(totalLength); let offset = 0; for (const chunk of chunks) { result.set(chunk, offset); offset += chunk.length; } return encodeBase64(result); } //# sourceMappingURL=encodeCredentials.js.map