88 lines
2.8 KiB
JavaScript
88 lines
2.8 KiB
JavaScript
import qrcode from '../vendor-qrcode-generator.js';
|
||
|
||
const TRANSFER_PREFIX = 'shine-key-transfer-v1:';
|
||
const encoder = new TextEncoder();
|
||
const decoder = new TextDecoder();
|
||
|
||
function bytesToBase64Url(bytes) {
|
||
let binary = '';
|
||
bytes.forEach((b) => {
|
||
binary += String.fromCharCode(b);
|
||
});
|
||
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
|
||
}
|
||
|
||
function base64UrlToBytes(value) {
|
||
const normalized = String(value || '').trim().replace(/-/g, '+').replace(/_/g, '/');
|
||
const padded = normalized + '='.repeat((4 - (normalized.length % 4)) % 4);
|
||
const binary = atob(padded);
|
||
const out = new Uint8Array(binary.length);
|
||
for (let i = 0; i < binary.length; i += 1) out[i] = binary.charCodeAt(i);
|
||
return out;
|
||
}
|
||
|
||
export function keyLabel(id) {
|
||
if (id === 'root') return 'root';
|
||
if (id === 'blockchain') return 'blockchain';
|
||
if (id === 'device') return 'device';
|
||
return id;
|
||
}
|
||
|
||
export function describeTransferKeys(keys = {}) {
|
||
const out = [];
|
||
if (keys.deviceKey) out.push('device');
|
||
if (keys.blockchainKey) out.push('blockchain');
|
||
if (keys.rootKey) out.push('root');
|
||
return out;
|
||
}
|
||
|
||
export function makeKeyTransferText({ login, keys }) {
|
||
const payload = {
|
||
v: 1,
|
||
type: 'shine-key-transfer',
|
||
login: String(login || '').trim(),
|
||
keys: {
|
||
deviceKey: String(keys?.deviceKey || ''),
|
||
blockchainKey: String(keys?.blockchainKey || ''),
|
||
rootKey: String(keys?.rootKey || ''),
|
||
},
|
||
createdAtMs: Date.now(),
|
||
};
|
||
const json = JSON.stringify(payload);
|
||
return `${TRANSFER_PREFIX}${bytesToBase64Url(encoder.encode(json))}`;
|
||
}
|
||
|
||
export function parseKeyTransferText(text) {
|
||
const raw = String(text || '').trim();
|
||
if (!raw.startsWith(TRANSFER_PREFIX)) {
|
||
throw new Error('Это не QR-код переноса ключей SHiNE');
|
||
}
|
||
const json = decoder.decode(base64UrlToBytes(raw.slice(TRANSFER_PREFIX.length)));
|
||
const payload = JSON.parse(json);
|
||
if (payload?.v !== 1 || payload?.type !== 'shine-key-transfer') {
|
||
throw new Error('Неподдерживаемый формат QR-кода');
|
||
}
|
||
const login = String(payload.login || '').trim();
|
||
if (!login) throw new Error('В QR-коде нет логина');
|
||
const keys = payload.keys && typeof payload.keys === 'object' ? payload.keys : {};
|
||
if (!keys.deviceKey && !keys.blockchainKey && !keys.rootKey) {
|
||
throw new Error('В QR-коде нет ключей');
|
||
}
|
||
return {
|
||
login,
|
||
keys: {
|
||
deviceKey: String(keys.deviceKey || ''),
|
||
blockchainKey: String(keys.blockchainKey || ''),
|
||
rootKey: String(keys.rootKey || ''),
|
||
},
|
||
keyTypes: describeTransferKeys(keys),
|
||
};
|
||
}
|
||
|
||
export function renderQrSvg(text, { cellSize = 4, margin = 4 } = {}) {
|
||
const qr = qrcode(0, 'L');
|
||
qr.addData(String(text || ''), 'Byte');
|
||
qr.make();
|
||
return qr.createSvgTag(cellSize, margin);
|
||
}
|