mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
checkpoint: secure assets and login flow baseline
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
const KEY = new TextEncoder().encode('slogga-dist-assets-2026');
|
||||
|
||||
export const encodeBytes = bytes =>
|
||||
{
|
||||
const output = new Uint8Array(bytes.length);
|
||||
|
||||
for(let index = 0; index < bytes.length; index++)
|
||||
{
|
||||
output[index] = bytes[index] ^ KEY[index % KEY.length] ^ ((index * 31) & 255);
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
import { encodeBytes } from './asset-codec.mjs';
|
||||
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { gzipSync } from 'zlib';
|
||||
|
||||
const dist = 'dist';
|
||||
const buildVersion = Date.now().toString(36);
|
||||
|
||||
const walk = dir =>
|
||||
{
|
||||
const out = [];
|
||||
|
||||
for(const entry of readdirSync(dir))
|
||||
{
|
||||
const path = join(dir, entry);
|
||||
const stat = statSync(path);
|
||||
|
||||
if(stat.isDirectory()) out.push(...walk(path));
|
||||
else out.push(path);
|
||||
}
|
||||
|
||||
return out;
|
||||
};
|
||||
|
||||
const minifyJson = path =>
|
||||
{
|
||||
try
|
||||
{
|
||||
writeFileSync(path, JSON.stringify(JSON.parse(readFileSync(path, 'utf8'))));
|
||||
}
|
||||
catch {}
|
||||
};
|
||||
|
||||
const encryptFile = path =>
|
||||
{
|
||||
const bytes = gzipSync(readFileSync(path), { level: 9 });
|
||||
writeFileSync(path + '.dat', encodeBytes(bytes));
|
||||
rmSync(path);
|
||||
};
|
||||
|
||||
if(!existsSync(dist)) throw new Error('dist folder not found');
|
||||
|
||||
for(const file of walk(dist))
|
||||
{
|
||||
if(file.endsWith('.json')) minifyJson(file);
|
||||
}
|
||||
|
||||
for(const file of [ 'renderer-config.json', 'ui-config.json' ])
|
||||
{
|
||||
const target = join(dist, file);
|
||||
if(existsSync(target)) rmSync(target);
|
||||
}
|
||||
|
||||
for(const file of walk(dist))
|
||||
{
|
||||
if(file.endsWith('.js') && !file.endsWith('asset-loader.js')) encryptFile(file);
|
||||
if(file.endsWith('.css')) encryptFile(file);
|
||||
}
|
||||
|
||||
const assetMirrorDir = join(dist, 'src', 'assets');
|
||||
mkdirSync(assetMirrorDir, { recursive: true });
|
||||
|
||||
for(const file of [ 'app.css.dat', 'app.js.dat' ])
|
||||
{
|
||||
const source = join(dist, 'assets', file);
|
||||
const target = join(assetMirrorDir, file);
|
||||
|
||||
if(existsSync(source)) copyFileSync(source, target);
|
||||
}
|
||||
|
||||
const publicLoaderAssets = [
|
||||
[ 'src/assets/images/loading/loading.gif', 'loading.gif' ],
|
||||
[ 'src/assets/images/notifications/nitro_v3.png', 'nitro_v3.png' ]
|
||||
];
|
||||
|
||||
for(const [ source, file ] of publicLoaderAssets)
|
||||
{
|
||||
const target = join(dist, 'assets', file);
|
||||
const mirrorTarget = join(assetMirrorDir, file);
|
||||
|
||||
if(existsSync(source))
|
||||
{
|
||||
copyFileSync(source, target);
|
||||
copyFileSync(source, mirrorTarget);
|
||||
}
|
||||
}
|
||||
|
||||
writeFileSync(join(dist, 'index.html'), `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"></head><body><div id="root"></div><script src="asset-loader.js?v=${ buildVersion }"></script></body></html>`);
|
||||
@@ -0,0 +1,8 @@
|
||||
import { mkdirSync, writeFileSync } from 'fs';
|
||||
import { dirname, resolve } from 'path';
|
||||
|
||||
const loader = `(()=>{const h=()=>{try{const s=new URLSearchParams(location.search);return s.get("loaderDebug")==="1"||localStorage.getItem("nitro.loader.debug")==="1"}catch{return!1}},m=t=>{if(!h()){document.getElementById("nitro-loader-debug")?.remove();return}let n=document.getElementById("nitro-loader-debug");if(!n){n=document.createElement("div");n.id="nitro-loader-debug";n.style.cssText="position:fixed;left:8px;top:8px;z-index:2147483647;padding:6px 8px;max-width:70vw;background:rgba(0,0,0,.85);color:#fff;font:12px monospace;white-space:pre-wrap";document.body.appendChild(n)}n.textContent=t},n=()=>{const s=document.currentScript?.src||location.href;return new URL(".",s)},v=()=>{const r=document.getElementById("root");if(!r||r.firstChild)return;r.innerHTML='<div style="position:fixed;inset:0;background:#6eadc8;overflow:hidden;z-index:1"><img src="https://hotel.slogga.it/client/nitro/images/reception/background_gradient_apr25.png" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center top" alt=""><img src="https://hotel.slogga.it/client/nitro/images/reception/mute_reception_backdrop_left.png" style="position:absolute;left:0;bottom:0;width:100%;height:100%;object-fit:none;object-position:left bottom" alt=""><img src="https://hotel.slogga.it/client/nitro/images/reception/background_right.png" style="position:absolute;right:0;bottom:0;width:400px;height:100%;object-fit:none;object-position:right bottom" alt=""><img src="https://hotel.slogga.it/client/nitro/images/reception/drape.png" style="position:absolute;left:0;top:0;width:190px;height:220px;object-fit:contain;object-position:left top" alt=""><div style="position:absolute;top:50%;right:8vw;transform:translateY(-50%);display:flex;flex-direction:column;gap:18px;width:260px"><div style="height:86px;background:#a2bfd1;border:2px solid #3f6a85;border-radius:8px;box-shadow:inset 0 2px rgba(255,255,255,.35),0 4px 6px rgba(0,0,0,.25)"></div><div style="height:190px;background:#a2bfd1;border:2px solid #3f6a85;border-radius:8px;box-shadow:inset 0 2px rgba(255,255,255,.35),0 4px 6px rgba(0,0,0,.25)"></div></div></div>'},k=new TextEncoder().encode("slogga-dist-assets-2026"),d=b=>{const o=new Uint8Array(b.length);for(let i=0;i<b.length;i++)o[i]=b[i]^k[i%k.length]^i*31&255;return o},z=async b=>{if(!("DecompressionStream" in self))throw new Error("gzip decompression unsupported");const s=new Blob([b]).stream().pipeThrough(new DecompressionStream("gzip"));return new Uint8Array(await new Response(s).arrayBuffer())},u=p=>{const b=n(),q=p.replace(/^\\.\\//,""),f=q.split("/").pop(),c=[new URL("./src/assets/"+f,b),new URL("./assets/"+f,b),new URL("/src/assets/"+f,b.origin),new URL("/assets/"+f,b.origin),new URL("/client/src/assets/"+f,b.origin),new URL("/client/assets/"+f,b.origin)];return[...new Map(c.map(x=>[x.href,x])).values()]},g=async p=>{let e=null;m("loader: fetching "+p);for(const a of u(p)){try{m("loader: try "+a.href);const r=await fetch(a,{cache:"no-store"});if(!r.ok){e=new Error("asset "+a.pathname+" "+r.status);continue}m("loader: ok "+a.href);return z(d(new Uint8Array(await r.arrayBuffer())))}catch(x){e=x}}throw e||new Error("asset "+p+" not found")},s=c=>{const l=document.createElement("style");l.textContent=new TextDecoder().decode(c);document.head.appendChild(l);m("loader: css injected")},j=async c=>{const u=URL.createObjectURL(new Blob([c],{type:"text/javascript"}));try{m("loader: importing app blob");await import(u);m("loader: app blob imported")}finally{URL.revokeObjectURL(u)}};(async()=>{m("loader: start");v();const[c,a]=await Promise.all([g("./assets/app.css.dat"),g("./assets/app.js.dat")]);s(c);await j(a)})().catch(e=>{console.error(e);m("loader: failed "+(e?.message||e));document.body.textContent="Unable to load client."})})();`;
|
||||
const target = resolve('public', 'asset-loader.js');
|
||||
|
||||
mkdirSync(dirname(target), { recursive: true });
|
||||
writeFileSync(target, loader);
|
||||
Reference in New Issue
Block a user