/** * the type for base 64 */ export type TStringInputType = 'string' | 'base64' | 'base64uri'; /** * Cross-platform base64 implementation * Works in both Node.js and browser environments */ const universalBase64 = { encode: (str: string): string => { if (typeof Buffer !== 'undefined') { // Node.js environment return Buffer.from(str, 'utf8').toString('base64'); } else if (typeof btoa !== 'undefined') { // Browser environment // Handle Unicode properly const utf8Bytes = new TextEncoder().encode(str); const binaryString = Array.from(utf8Bytes, byte => String.fromCharCode(byte)).join(''); return btoa(binaryString); } else { // Fallback pure JS implementation const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; const bytes = new TextEncoder().encode(str); let result = ''; let i = 0; while (i < bytes.length) { const a = bytes[i++]; const b = i < bytes.length ? bytes[i++] : 0; const c = i < bytes.length ? bytes[i++] : 0; const bitmap = (a << 16) | (b << 8) | c; result += chars.charAt((bitmap >> 18) & 63); result += chars.charAt((bitmap >> 12) & 63); result += i - 2 < bytes.length ? chars.charAt((bitmap >> 6) & 63) : '='; result += i - 1 < bytes.length ? chars.charAt(bitmap & 63) : '='; } return result; } }, decode: (str: string): string => { // Handle base64uri by converting back to standard base64 const base64String = str .replace(/-/g, '+') .replace(/_/g, '/') .padEnd(str.length + ((4 - (str.length % 4)) % 4), '='); if (typeof Buffer !== 'undefined') { // Node.js environment return Buffer.from(base64String, 'base64').toString('utf8'); } else if (typeof atob !== 'undefined') { // Browser environment const binaryString = atob(base64String); const bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); } return new TextDecoder().decode(bytes); } else { // Fallback pure JS implementation const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; let bytes: number[] = []; let i = 0; while (i < base64String.length) { const encoded1 = chars.indexOf(base64String.charAt(i++)); const encoded2 = chars.indexOf(base64String.charAt(i++)); const encoded3 = chars.indexOf(base64String.charAt(i++)); const encoded4 = chars.indexOf(base64String.charAt(i++)); const bitmap = (encoded1 << 18) | (encoded2 << 12) | (encoded3 << 6) | encoded4; bytes.push((bitmap >> 16) & 255); if (encoded3 !== 64) bytes.push((bitmap >> 8) & 255); if (encoded4 !== 64) bytes.push(bitmap & 255); } return new TextDecoder().decode(new Uint8Array(bytes)); } } }; /** * handle base64 strings */ export class Base64 { private refString: string; constructor(inputStringArg, typeArg: TStringInputType) { switch (typeArg) { case 'string': // easiest case this.refString = inputStringArg; break; case 'base64': this.refString = base64.decode(inputStringArg); break; case 'base64uri': this.refString = base64.decode(inputStringArg); } } /** * the simple string (unencoded) */ get simpleString() { return this.refString; } /** * the base64 encoded version of the original string */ get base64String() { return base64.encode(this.refString); } /** * the base64uri encoded version of the original string */ get base64UriString() { return base64.encodeUri(this.refString); } } export let base64 = { /** * encodes the string */ encode: (stringArg: string) => { return universalBase64.encode(stringArg); }, /** * encodes a stringArg to base64 uri style */ encodeUri: (stringArg: string) => { return universalBase64.encode(stringArg) .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=/g, ''); }, /** * decodes a base64 encoded string */ decode: (stringArg: string) => { return universalBase64.decode(stringArg); }, /** * * @param stringArg * checks wether the string is base64 encoded */ isBase64: (stringArg: string) => { const regex = /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$/; return regex.test(stringArg); }, };