2fa20da94d
- Main: license service, IPC, router; закрытие окон; yauzl закрытие zip (EMFILE), zipRead тест - Editor: стабильный projectState без мигания, логотип и меню, строки UI, LayoutShell overlay - Control: ластик для всех типов эффектов, затухание/нарастание музыки при смене сцены - Сборка: vite, build/dev scripts, obfuscate-main и build-env скрипты с тестами; package.json Made-with: Cursor
20 lines
905 B
TypeScript
20 lines
905 B
TypeScript
/** Детерминированная JSON-сериализация для подписи (совпадает с `lib/canonicalJson.mjs` в DndGamePlayerLicenseServer). */
|
||
export function canonicalJson(value: unknown): string {
|
||
if (value === null) return 'null';
|
||
const t = typeof value;
|
||
if (t === 'number') {
|
||
if (!Number.isFinite(value as number)) throw new TypeError('non-finite number in license payload');
|
||
return JSON.stringify(value);
|
||
}
|
||
if (t === 'string' || t === 'boolean') return JSON.stringify(value);
|
||
if (Array.isArray(value)) {
|
||
return `[${value.map((x) => canonicalJson(x)).join(',')}]`;
|
||
}
|
||
if (t === 'object') {
|
||
const o = value as Record<string, unknown>;
|
||
const keys = Object.keys(o).sort();
|
||
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(o[k])}`).join(',')}}`;
|
||
}
|
||
throw new TypeError(`unsupported type in license payload: ${t}`);
|
||
}
|