/* 388 보안 — 진짜 TS(b_page_state · b_project_switch · design_store)를 가짜 저장소 위에서 돌려 * JSON 으로 냄: 로그아웃 지움 · 계정 바뀜 지움 · 세션 넘침 · 탭이 연 프로젝트 고정. */ const fs = require("fs"); const path = require("path"); const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); const ROOT = path.join(__dirname, "..", ".."); const load = (rel, requireStub) => { const js = ts.transpileModule(fs.readFileSync(path.join(ROOT, rel), "utf8"), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, }).outputText; const exports = {}; new Function("exports", "require", "window", js)(exports, requireStub, global.window); return exports; }; /** 진짜 Storage 처럼 Object.keys 가 열쇠를 냄 · limit 글자 넘으면 QuotaExceededError. */ function fakeStorage(limit = Infinity) { const data = {}; const size = () => Object.entries(data).reduce((n, [k, v]) => n + k.length + v.length, 0); const api = { length: { get: () => Object.keys(data).length }, key: { value: (i) => Object.keys(data)[i] ?? null }, getItem: { value: (k) => (k in data ? data[k] : null) }, setItem: { value: (k, v) => { const old = data[k]; data[k] = String(v); if (size() > limit) { if (old === undefined) delete data[k]; else data[k] = old; const error = new Error("full"); error.name = "QuotaExceededError"; throw error; } }, }, removeItem: { value: (k) => delete data[k] }, clear: { value: () => Object.keys(data).forEach((k) => delete data[k]) }, }; return new Proxy(data, { get: (t, p) => (p in api ? (api[p].get ? api[p].get() : api[p].value) : t[p]), }); } global.window = { sessionStorage: fakeStorage(), localStorage: fakeStorage() }; const toasts = []; const pageState = load("A00_Common/b_page_state.ts", () => ({ t: (key) => key, showToast: (msg, kind) => toasts.push([msg, kind]), })); const purged = []; const config = { CURRENT_PROJECT_ID_KEY: "frd_current_project_id" }; const sw = load("A00_Common/b_project_switch.ts", (id) => { if (id.includes("b_page_state")) return pageState; if (id.includes("b_asset_cache")) return { purgeOtherProjects: async (p) => purged.push(p) }; if (id.includes("config_frontend")) return config; return { showConfirmDialog: async () => true }; }); const store = load("A00_Common/design/design_store.ts", (id) => id.includes("b_page_state") ? pageState : config, ); const P = "aaaa1111"; const seed = () => { global.window.sessionStorage = fakeStorage(); const s = global.window.sessionStorage; s.setItem(`aislo:draft:structures:${P}`, '[{"id":1}]'); s.setItem(`aislo:result:section-detail:${P}:7`, '{"d":1}'); s.setItem("aislo:entered-project", P); s.setItem("b05:pipes", "[1]"); global.window.localStorage.setItem("frd_current_project_id", P); global.window.localStorage.setItem("aislo:pref:panel:x", "1"); purged.length = 0; }; const sessionKeys = () => Object.keys(global.window.sessionStorage).sort(); const local = (k) => global.window.localStorage.getItem(k); const tick = () => new Promise((r) => setTimeout(r, 0)); (async () => { const out = {}; seed(); await sw.wipeAccountCache(); out.logout = { session: sessionKeys(), project: local("frd_current_project_id"), pref: local("aislo:pref:panel:x"), purged: [...purged], }; global.window.localStorage.removeItem("aislo:owner"); seed(); out.owner = { first: await sw.wipeIfOwnerChanged("u1"), firstKeys: sessionKeys().length }; seed(); out.owner.same = await sw.wipeIfOwnerChanged("u1"); out.owner.sameKeys = sessionKeys().length; out.owner.other = await sw.wipeIfOwnerChanged("u2"); out.owner.otherKeys = sessionKeys().length; out.owner.otherProject = local("frd_current_project_id"); out.owner.stored = local("aislo:owner"); // 서버 id 가 수로 옴 — 같은 계정 수 id 는 두 번째에 안 지움(새로고침 되풀이 막음) await sw.wipeIfOwnerChanged(3); out.owner.numericAgain = await sw.wipeIfOwnerChanged(3); // 세션 넘침 — 결과 칸 비우고 다시 · 그래도 넘치면 안내 global.window.sessionStorage = fakeStorage(400); const s = global.window.sessionStorage; s.setItem(`aislo:result:section-detail:${P}:7`, "r".repeat(200)); s.setItem(`aislo:draft:structures:${P}`, "d".repeat(50)); pageState.writeByKey(`aislo:draft:crossw:${P}:7`, "c".repeat(100)); out.overflow = { saved: s.getItem(`aislo:draft:crossw:${P}:7`)?.length ?? 0, result: s.getItem(`aislo:result:section-detail:${P}:7`), draft: s.getItem(`aislo:draft:structures:${P}`)?.length ?? 0, toasts: toasts.length, }; pageState.writeByKey(`aislo:draft:crossw:${P}:8`, "c".repeat(500)); await tick(); out.full = { saved: s.getItem(`aislo:draft:crossw:${P}:8`), toasts: [...toasts] }; // 탭이 연 프로젝트 고정 — 다른 탭이 현재 프로젝트를 바꿔도 그대로 global.window.localStorage.setItem("frd_current_project_id", "first"); out.pin = { before: store.draftProjectId() }; store.pinDraftProject("first"); global.window.localStorage.setItem("frd_current_project_id", "other-tab"); out.pin.after = store.draftProjectId(); out.pin.explicit = store.draftProjectId("given"); process.stdout.write(JSON.stringify(out)); })();