const B64URL = { encode(bytes: Uint8Array): string { let bin = ''; for (const byte of bytes) { bin += String.fromCharCode(byte); } const b64 = btoa(bin); return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/u, ''); }, decode(s: string): Uint8Array { const pad = s.length % 4 === 0 ? '' : '='.repeat(4 - (s.length % 4)); const b64 = s.replace(/-/g, '+').replace(/_/g, '/') + pad; const bin = atob(b64); const out = new Uint8Array(bin.length); for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i) & 0xff; return out; }, }; /** Тело UTF-8 + подпись Ed25519 (64 байта), разделитель «.». */ export function splitSignedLicenseToken(token: string): { bodyUtf8: string; signature: Uint8Array } | null { const t = token.trim(); const dot = t.indexOf('.'); if (dot <= 0) return null; const a = t.slice(0, dot); const b = t.slice(dot + 1); if (!a || !b) return null; try { const bodyBytes = B64URL.decode(a); const sigBytes = B64URL.decode(b); const bodyUtf8 = new TextDecoder().decode(bodyBytes); return { bodyUtf8, signature: sigBytes }; } catch { return null; } } export function joinSignedLicenseToken(bodyUtf8: string, signature: Uint8Array): string { const bodyBytes = new TextEncoder().encode(bodyUtf8); return `${B64URL.encode(bodyBytes)}.${B64URL.encode(signature)}`; }