Compare commits
No commits in common. "18dcf789c88e636bfcb04da5eb5801325cdbd078" and "9f7244185d74b4f09eeeff1005e096366177f840" have entirely different histories.
18dcf789c8
...
9f7244185d
190
src/main/main.ts
190
src/main/main.ts
|
|
@ -22,7 +22,6 @@ const SERVER_HEALTHCHECK_TIMEOUT_MS = APP_CONFIG.serverHealthcheckTimeoutMs;
|
||||||
const SERVER_REQUEST_TIMEOUT_MS = APP_CONFIG.serverRequestTimeoutMs;
|
const SERVER_REQUEST_TIMEOUT_MS = APP_CONFIG.serverRequestTimeoutMs;
|
||||||
const COMMAND_TIMEOUT_MS = APP_CONFIG.commandTimeoutMs;
|
const COMMAND_TIMEOUT_MS = APP_CONFIG.commandTimeoutMs;
|
||||||
const INSTALL_TIMEOUT_MS = APP_CONFIG.installTimeoutMs;
|
const INSTALL_TIMEOUT_MS = APP_CONFIG.installTimeoutMs;
|
||||||
const SESSION_TTL_MS = APP_CONFIG.sessionTtlMs;
|
|
||||||
|
|
||||||
type CommandCheckResult = {
|
type CommandCheckResult = {
|
||||||
ok: boolean;
|
ok: boolean;
|
||||||
|
|
@ -72,13 +71,6 @@ type SptInstallInfo = {
|
||||||
error?: string;
|
error?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SessionRecord = {
|
|
||||||
username: string;
|
|
||||||
sessionId: string;
|
|
||||||
expiresAt: number;
|
|
||||||
updatedAt: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type InstallStep = {
|
type InstallStep = {
|
||||||
name: string;
|
name: string;
|
||||||
result: CommandRunResult;
|
result: CommandRunResult;
|
||||||
|
|
@ -232,63 +224,11 @@ const getGitPaths = async () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const MOD_VERSION_FILE_NAME = "mod-version.json";
|
const MOD_VERSION_FILE_NAME = "mod-version.json";
|
||||||
const SESSION_FILE_NAME = "session.json";
|
|
||||||
|
|
||||||
const getModVersionFilePath = () => {
|
const getModVersionFilePath = () => {
|
||||||
return path.join(app.getPath("userData"), MOD_VERSION_FILE_NAME);
|
return path.join(app.getPath("userData"), MOD_VERSION_FILE_NAME);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSessionRecordPath = () => {
|
|
||||||
return path.join(app.getPath("userData"), SESSION_FILE_NAME);
|
|
||||||
};
|
|
||||||
|
|
||||||
const readSessionRecord = async (): Promise<SessionRecord | null> => {
|
|
||||||
const filePath = getSessionRecordPath();
|
|
||||||
try {
|
|
||||||
const raw = await fs.readFile(filePath, "utf8");
|
|
||||||
const parsed = JSON.parse(raw) as SessionRecord;
|
|
||||||
if (
|
|
||||||
typeof parsed.username !== "string" ||
|
|
||||||
typeof parsed.sessionId !== "string" ||
|
|
||||||
typeof parsed.expiresAt !== "number" ||
|
|
||||||
typeof parsed.updatedAt !== "number"
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return parsed;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const writeSessionRecord = async (payload: { username: string; sessionId: string }) => {
|
|
||||||
const filePath = getSessionRecordPath();
|
|
||||||
const record: SessionRecord = {
|
|
||||||
username: payload.username,
|
|
||||||
sessionId: payload.sessionId,
|
|
||||||
updatedAt: Date.now(),
|
|
||||||
expiresAt: Date.now() + SESSION_TTL_MS
|
|
||||||
};
|
|
||||||
await fs.writeFile(filePath, JSON.stringify(record, null, 2), "utf8");
|
|
||||||
return record;
|
|
||||||
};
|
|
||||||
|
|
||||||
const refreshSessionRecord = async (payload: { username: string; sessionId: string }) => {
|
|
||||||
return await writeSessionRecord(payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
const clearSessionRecord = async () => {
|
|
||||||
const filePath = getSessionRecordPath();
|
|
||||||
await fs.rm(filePath, { force: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
const isSessionExpired = (record: SessionRecord | null) => {
|
|
||||||
if (!record) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return record.expiresAt <= Date.now();
|
|
||||||
};
|
|
||||||
|
|
||||||
const readModVersionRecord = async () => {
|
const readModVersionRecord = async () => {
|
||||||
const filePath = getModVersionFilePath();
|
const filePath = getModVersionFilePath();
|
||||||
try {
|
try {
|
||||||
|
|
@ -950,28 +890,6 @@ const requestSessionId = async (username: string) => {
|
||||||
return { ok: true, sessionId, url: loginResult.url };
|
return { ok: true, sessionId, url: loginResult.url };
|
||||||
};
|
};
|
||||||
|
|
||||||
const isSessionInvalidStatus = (status?: number) => status === 401 || status === 403;
|
|
||||||
|
|
||||||
const getSessionIdForUser = async (username: string) => {
|
|
||||||
const cached = await readSessionRecord();
|
|
||||||
if (cached && cached.username === username && !isSessionExpired(cached)) {
|
|
||||||
await refreshSessionRecord({ username: cached.username, sessionId: cached.sessionId });
|
|
||||||
return { ok: true, sessionId: cached.sessionId, url: SERVER_BASE_URL, source: "cache" as const };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cached && cached.username !== username) {
|
|
||||||
await clearSessionRecord();
|
|
||||||
}
|
|
||||||
|
|
||||||
const loginResult = await requestSessionId(username);
|
|
||||||
if (!loginResult.ok) {
|
|
||||||
return loginResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
await writeSessionRecord({ username, sessionId: loginResult.sessionId });
|
|
||||||
return { ok: true, sessionId: loginResult.sessionId, url: loginResult.url, source: "login" as const };
|
|
||||||
};
|
|
||||||
|
|
||||||
const registerProfile = async (username: string) => {
|
const registerProfile = async (username: string) => {
|
||||||
return await postJson("/launcher/profile/register", {
|
return await postJson("/launcher/profile/register", {
|
||||||
username,
|
username,
|
||||||
|
|
@ -1117,7 +1035,6 @@ app.whenReady().then(() => {
|
||||||
|
|
||||||
ipcMain.handle("spt:fetchProfile", async (_event, payload: { username: string }) => {
|
ipcMain.handle("spt:fetchProfile", async (_event, payload: { username: string }) => {
|
||||||
const username = payload.username.trim();
|
const username = payload.username.trim();
|
||||||
let activeSessionId: string | undefined;
|
|
||||||
|
|
||||||
const fetchProfileInfo = async (): Promise<{
|
const fetchProfileInfo = async (): Promise<{
|
||||||
ok: boolean;
|
ok: boolean;
|
||||||
|
|
@ -1126,7 +1043,7 @@ app.whenReady().then(() => {
|
||||||
error?: string;
|
error?: string;
|
||||||
url: string;
|
url: string;
|
||||||
}> => {
|
}> => {
|
||||||
const sessionResult = await getSessionIdForUser(username);
|
const sessionResult = await requestSessionId(username);
|
||||||
if (!sessionResult.ok) {
|
if (!sessionResult.ok) {
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
|
|
@ -1134,7 +1051,6 @@ app.whenReady().then(() => {
|
||||||
url: sessionResult.url
|
url: sessionResult.url
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
activeSessionId = sessionResult.sessionId;
|
|
||||||
|
|
||||||
return await postJson(
|
return await postJson(
|
||||||
"/launcher/profile/info",
|
"/launcher/profile/info",
|
||||||
|
|
@ -1145,30 +1061,8 @@ app.whenReady().then(() => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
let infoResult = await fetchProfileInfo();
|
const infoResult = await fetchProfileInfo();
|
||||||
if (!infoResult.ok && isSessionInvalidStatus(infoResult.status)) {
|
|
||||||
const relogin = await requestSessionId(username);
|
|
||||||
if (!relogin.ok) {
|
|
||||||
return {
|
|
||||||
ok: false,
|
|
||||||
error: relogin.error ?? "login_failed",
|
|
||||||
url: relogin.url
|
|
||||||
};
|
|
||||||
}
|
|
||||||
await writeSessionRecord({ username, sessionId: relogin.sessionId });
|
|
||||||
activeSessionId = relogin.sessionId;
|
|
||||||
infoResult = await postJson(
|
|
||||||
"/launcher/profile/info",
|
|
||||||
{ username },
|
|
||||||
{
|
|
||||||
Cookie: `PHPSESSID=${relogin.sessionId}`
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (infoResult.ok && infoResult.data) {
|
if (infoResult.ok && infoResult.data) {
|
||||||
if (activeSessionId) {
|
|
||||||
await refreshSessionRecord({ username, sessionId: activeSessionId });
|
|
||||||
}
|
|
||||||
return infoResult;
|
return infoResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1179,13 +1073,12 @@ app.whenReady().then(() => {
|
||||||
ipcMain.handle("spt:downloadProfile", async (_event, payload: { username: string }) => {
|
ipcMain.handle("spt:downloadProfile", async (_event, payload: { username: string }) => {
|
||||||
const username = payload.username.trim();
|
const username = payload.username.trim();
|
||||||
|
|
||||||
const sessionResult = await getSessionIdForUser(username);
|
const sessionResult = await requestSessionId(username);
|
||||||
if (!sessionResult.ok) {
|
if (!sessionResult.ok) {
|
||||||
return sessionResult;
|
return sessionResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
let activeSessionId = sessionResult.sessionId;
|
const infoResult = await postJson(
|
||||||
let infoResult = await postJson(
|
|
||||||
"/launcher/profile/info",
|
"/launcher/profile/info",
|
||||||
{ username },
|
{ username },
|
||||||
{
|
{
|
||||||
|
|
@ -1193,28 +1086,10 @@ app.whenReady().then(() => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!infoResult.ok && isSessionInvalidStatus(infoResult.status)) {
|
|
||||||
const relogin = await requestSessionId(username);
|
|
||||||
if (!relogin.ok) {
|
|
||||||
return relogin;
|
|
||||||
}
|
|
||||||
await writeSessionRecord({ username, sessionId: relogin.sessionId });
|
|
||||||
activeSessionId = relogin.sessionId;
|
|
||||||
infoResult = await postJson(
|
|
||||||
"/launcher/profile/info",
|
|
||||||
{ username },
|
|
||||||
{
|
|
||||||
Cookie: `PHPSESSID=${relogin.sessionId}`
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!infoResult.ok || !infoResult.data) {
|
if (!infoResult.ok || !infoResult.data) {
|
||||||
return infoResult;
|
return infoResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
await refreshSessionRecord({ username, sessionId: activeSessionId });
|
|
||||||
|
|
||||||
const downloadsDir = app.getPath("downloads");
|
const downloadsDir = app.getPath("downloads");
|
||||||
const fileName = `spt-profile-${username}-${Date.now()}.json`;
|
const fileName = `spt-profile-${username}-${Date.now()}.json`;
|
||||||
const filePath = path.join(downloadsDir, fileName);
|
const filePath = path.join(downloadsDir, fileName);
|
||||||
|
|
@ -1232,71 +1107,18 @@ app.whenReady().then(() => {
|
||||||
ipcMain.handle("spt:resetProfile", async (_event, payload: { username: string }) => {
|
ipcMain.handle("spt:resetProfile", async (_event, payload: { username: string }) => {
|
||||||
const username = payload.username.trim();
|
const username = payload.username.trim();
|
||||||
|
|
||||||
const sessionResult = await getSessionIdForUser(username);
|
const sessionResult = await requestSessionId(username);
|
||||||
if (!sessionResult.ok) {
|
if (!sessionResult.ok) {
|
||||||
return sessionResult;
|
return sessionResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
let activeSessionId = sessionResult.sessionId;
|
return await postJson(
|
||||||
let wipeResult = await postJson(
|
|
||||||
"/launcher/profile/change/wipe",
|
"/launcher/profile/change/wipe",
|
||||||
{ username },
|
{ username },
|
||||||
{
|
{
|
||||||
Cookie: `PHPSESSID=${sessionResult.sessionId}`
|
Cookie: `PHPSESSID=${sessionResult.sessionId}`
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!wipeResult.ok && isSessionInvalidStatus(wipeResult.status)) {
|
|
||||||
const relogin = await requestSessionId(username);
|
|
||||||
if (!relogin.ok) {
|
|
||||||
return relogin;
|
|
||||||
}
|
|
||||||
await writeSessionRecord({ username, sessionId: relogin.sessionId });
|
|
||||||
activeSessionId = relogin.sessionId;
|
|
||||||
wipeResult = await postJson(
|
|
||||||
"/launcher/profile/change/wipe",
|
|
||||||
{ username },
|
|
||||||
{
|
|
||||||
Cookie: `PHPSESSID=${relogin.sessionId}`
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wipeResult.ok) {
|
|
||||||
await refreshSessionRecord({ username, sessionId: activeSessionId });
|
|
||||||
}
|
|
||||||
return wipeResult;
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle("spt:resumeSession", async () => {
|
|
||||||
const record = await readSessionRecord();
|
|
||||||
if (!record || isSessionExpired(record)) {
|
|
||||||
await clearSessionRecord();
|
|
||||||
return { ok: false, error: "session_expired", url: SERVER_BASE_URL };
|
|
||||||
}
|
|
||||||
|
|
||||||
const infoResult = await postJson(
|
|
||||||
"/launcher/profile/info",
|
|
||||||
{ username: record.username },
|
|
||||||
{
|
|
||||||
Cookie: `PHPSESSID=${record.sessionId}`
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!infoResult.ok && isSessionInvalidStatus(infoResult.status)) {
|
|
||||||
await clearSessionRecord();
|
|
||||||
return { ok: false, error: "session_expired", url: infoResult.url };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (infoResult.ok) {
|
|
||||||
await refreshSessionRecord({ username: record.username, sessionId: record.sessionId });
|
|
||||||
}
|
|
||||||
return infoResult;
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle("spt:clearSession", async () => {
|
|
||||||
await clearSessionRecord();
|
|
||||||
return { ok: true };
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 개발/운영 모두에서 부팅 시 1회 즉시 체크(instant check) 로그를 남겨
|
// 개발/운영 모두에서 부팅 시 1회 즉시 체크(instant check) 로그를 남겨
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,5 @@ contextBridge.exposeInMainWorld("sptLauncher", {
|
||||||
downloadProfile: (payload: { username: string }) =>
|
downloadProfile: (payload: { username: string }) =>
|
||||||
ipcRenderer.invoke("spt:downloadProfile", payload),
|
ipcRenderer.invoke("spt:downloadProfile", payload),
|
||||||
resetProfile: (payload: { username: string }) =>
|
resetProfile: (payload: { username: string }) =>
|
||||||
ipcRenderer.invoke("spt:resetProfile", payload),
|
ipcRenderer.invoke("spt:resetProfile", payload)
|
||||||
resumeSession: () => ipcRenderer.invoke("spt:resumeSession"),
|
|
||||||
clearSession: () => ipcRenderer.invoke("spt:clearSession")
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -97,56 +97,9 @@ const App = () => {
|
||||||
const gitCheckedRef = useRef(false);
|
const gitCheckedRef = useRef(false);
|
||||||
const modCheckInFlightRef = useRef(false);
|
const modCheckInFlightRef = useRef(false);
|
||||||
const modCheckedRef = useRef(false);
|
const modCheckedRef = useRef(false);
|
||||||
const sessionResumeAttemptedRef = useRef(false);
|
|
||||||
const transitionTimeoutRef = useRef<number | undefined>();
|
const transitionTimeoutRef = useRef<number | undefined>();
|
||||||
const simulateToolsMissingRef = useRef(false);
|
const simulateToolsMissingRef = useRef(false);
|
||||||
|
|
||||||
const asObject = (value: unknown) =>
|
|
||||||
value && typeof value === "object" ? (value as Record<string, unknown>) : undefined;
|
|
||||||
const pickString = (...values: unknown[]) =>
|
|
||||||
values.find((value) => typeof value === "string" && value.trim().length > 0) as
|
|
||||||
| string
|
|
||||||
| undefined;
|
|
||||||
const pickNumber = (...values: unknown[]) => {
|
|
||||||
const candidate = values.find(
|
|
||||||
(value) => typeof value === "number" || (typeof value === "string" && value.trim() !== "")
|
|
||||||
);
|
|
||||||
if (typeof candidate === "number") {
|
|
||||||
return candidate;
|
|
||||||
}
|
|
||||||
if (typeof candidate === "string") {
|
|
||||||
const parsed = Number(candidate);
|
|
||||||
return Number.isFinite(parsed) ? parsed : undefined;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const extractProfile = (data: unknown, fallbackUsername: string) => {
|
|
||||||
const root = asObject(data);
|
|
||||||
const profileCandidate = Array.isArray(root?.profiles)
|
|
||||||
? root?.profiles?.[0]
|
|
||||||
: root?.profile ?? root?.data ?? root;
|
|
||||||
const profileObject = asObject(profileCandidate) ?? {};
|
|
||||||
const info = asObject(profileObject.info ?? profileObject.Info) ?? profileObject;
|
|
||||||
|
|
||||||
return {
|
|
||||||
username: pickString(info.username, info.Username, profileObject.username, fallbackUsername) ?? fallbackUsername,
|
|
||||||
nickname: pickString(info.nickname, info.Nickname, profileObject.nickname, profileObject.Nickname),
|
|
||||||
level: pickNumber(
|
|
||||||
info.level,
|
|
||||||
info.Level,
|
|
||||||
info.currlvl,
|
|
||||||
info.currLvl,
|
|
||||||
profileObject.level,
|
|
||||||
profileObject.Level,
|
|
||||||
profileObject.currlvl,
|
|
||||||
profileObject.currLvl
|
|
||||||
),
|
|
||||||
side: pickString(info.side, info.Side, profileObject.side, profileObject.Side),
|
|
||||||
id: pickString(info.id, info._id, profileObject.id, profileObject._id, profileObject.profileId)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const runGitCheck = useCallback(async (force = false) => {
|
const runGitCheck = useCallback(async (force = false) => {
|
||||||
if (force) {
|
if (force) {
|
||||||
gitCheckedRef.current = false;
|
gitCheckedRef.current = false;
|
||||||
|
|
@ -160,7 +113,7 @@ const App = () => {
|
||||||
setGitCheckInProgress(true);
|
setGitCheckInProgress(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (DEV_UI_ENABLED && simulateToolsMissingRef.current) {
|
if (IS_DEV && simulateToolsMissingRef.current) {
|
||||||
const simulated = {
|
const simulated = {
|
||||||
git: { ok: false, command: "git", error: "simulated_missing" },
|
git: { ok: false, command: "git", error: "simulated_missing" },
|
||||||
lfs: { ok: false, command: "git lfs", error: "simulated_missing" },
|
lfs: { ok: false, command: "git lfs", error: "simulated_missing" },
|
||||||
|
|
@ -302,7 +255,6 @@ const App = () => {
|
||||||
setProfile(undefined);
|
setProfile(undefined);
|
||||||
setSkipToolsCheck(false);
|
setSkipToolsCheck(false);
|
||||||
setDevProceedReady(false);
|
setDevProceedReady(false);
|
||||||
sessionResumeAttemptedRef.current = false;
|
|
||||||
setScreen("serverCheck");
|
setScreen("serverCheck");
|
||||||
clearTransitionTimeout();
|
clearTransitionTimeout();
|
||||||
},
|
},
|
||||||
|
|
@ -351,7 +303,7 @@ const App = () => {
|
||||||
|
|
||||||
if (serverRecoveryActive) {
|
if (serverRecoveryActive) {
|
||||||
setServerRecoveryActive(false);
|
setServerRecoveryActive(false);
|
||||||
if (DEV_UI_ENABLED) {
|
if (IS_DEV) {
|
||||||
setDevProceedReady(true);
|
setDevProceedReady(true);
|
||||||
} else {
|
} else {
|
||||||
scheduleScreenTransition("login");
|
scheduleScreenTransition("login");
|
||||||
|
|
@ -363,7 +315,7 @@ const App = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DEV_UI_ENABLED) {
|
if (IS_DEV) {
|
||||||
setDevProceedReady(true);
|
setDevProceedReady(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -412,50 +364,6 @@ const App = () => {
|
||||||
void runHealthCheck(serverRecoveryActive ? "recovery" : "initial");
|
void runHealthCheck(serverRecoveryActive ? "recovery" : "initial");
|
||||||
}, [screen, serverRecoveryActive, runHealthCheck]);
|
}, [screen, serverRecoveryActive, runHealthCheck]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
screen !== "serverCheck" ||
|
|
||||||
!serverHealthy ||
|
|
||||||
hasSession ||
|
|
||||||
serverCheckInProgress ||
|
|
||||||
!serverCheckedAt
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (sessionResumeAttemptedRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!window.sptLauncher?.resumeSession) {
|
|
||||||
sessionResumeAttemptedRef.current = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionResumeAttemptedRef.current = true;
|
|
||||||
void (async () => {
|
|
||||||
const result = await window.sptLauncher.resumeSession();
|
|
||||||
if (!result.ok || !result.data) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const resumedProfile = extractProfile(result.data, loginId.trim());
|
|
||||||
setProfile(resumedProfile);
|
|
||||||
setHasSession(true);
|
|
||||||
if (resumedProfile.username) {
|
|
||||||
setLoginId(resumedProfile.username);
|
|
||||||
}
|
|
||||||
clearTransitionTimeout();
|
|
||||||
setScreen("main");
|
|
||||||
})();
|
|
||||||
}, [
|
|
||||||
screen,
|
|
||||||
serverHealthy,
|
|
||||||
hasSession,
|
|
||||||
serverCheckInProgress,
|
|
||||||
serverCheckedAt,
|
|
||||||
loginId,
|
|
||||||
clearTransitionTimeout,
|
|
||||||
extractProfile
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (screen !== "serverCheck") {
|
if (screen !== "serverCheck") {
|
||||||
return;
|
return;
|
||||||
|
|
@ -618,6 +526,52 @@ const App = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const asObject = (value: unknown) =>
|
||||||
|
value && typeof value === "object" ? (value as Record<string, unknown>) : undefined;
|
||||||
|
const pickString = (...values: unknown[]) =>
|
||||||
|
values.find((value) => typeof value === "string" && value.trim().length > 0) as
|
||||||
|
| string
|
||||||
|
| undefined;
|
||||||
|
const pickNumber = (...values: unknown[]) => {
|
||||||
|
const candidate = values.find(
|
||||||
|
(value) => typeof value === "number" || (typeof value === "string" && value.trim() !== "")
|
||||||
|
);
|
||||||
|
if (typeof candidate === "number") {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
if (typeof candidate === "string") {
|
||||||
|
const parsed = Number(candidate);
|
||||||
|
return Number.isFinite(parsed) ? parsed : undefined;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const extractProfile = (data: unknown, fallbackUsername: string) => {
|
||||||
|
const root = asObject(data);
|
||||||
|
const profileCandidate = Array.isArray(root?.profiles)
|
||||||
|
? root?.profiles?.[0]
|
||||||
|
: root?.profile ?? root?.data ?? root;
|
||||||
|
const profileObject = asObject(profileCandidate) ?? {};
|
||||||
|
const info = asObject(profileObject.info ?? profileObject.Info) ?? profileObject;
|
||||||
|
|
||||||
|
return {
|
||||||
|
username: pickString(info.username, info.Username, profileObject.username, fallbackUsername) ?? fallbackUsername,
|
||||||
|
nickname: pickString(info.nickname, info.Nickname, profileObject.nickname, profileObject.Nickname),
|
||||||
|
level: pickNumber(
|
||||||
|
info.level,
|
||||||
|
info.Level,
|
||||||
|
info.currlvl,
|
||||||
|
info.currLvl,
|
||||||
|
profileObject.level,
|
||||||
|
profileObject.Level,
|
||||||
|
profileObject.currlvl,
|
||||||
|
profileObject.currLvl
|
||||||
|
),
|
||||||
|
side: pickString(info.side, info.Side, profileObject.side, profileObject.Side),
|
||||||
|
id: pickString(info.id, info._id, profileObject.id, profileObject._id, profileObject.profileId)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const handleLogin = async () => {
|
const handleLogin = async () => {
|
||||||
const trimmedId = loginId.trim();
|
const trimmedId = loginId.trim();
|
||||||
if (!trimmedId) {
|
if (!trimmedId) {
|
||||||
|
|
@ -676,9 +630,6 @@ const App = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
if (window.sptLauncher?.clearSession) {
|
|
||||||
void window.sptLauncher.clearSession();
|
|
||||||
}
|
|
||||||
setHasSession(false);
|
setHasSession(false);
|
||||||
setProfile(undefined);
|
setProfile(undefined);
|
||||||
setScreen("login");
|
setScreen("login");
|
||||||
|
|
@ -919,9 +870,7 @@ const App = () => {
|
||||||
className="ghost"
|
className="ghost"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSkipToolsCheck(true);
|
setSkipToolsCheck(true);
|
||||||
scheduleScreenTransition(
|
scheduleScreenTransition(IS_DEV ? "login" : hasSession ? "main" : "login");
|
||||||
DEV_UI_ENABLED ? "login" : hasSession ? "main" : "login"
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
나중에 진행
|
나중에 진행
|
||||||
|
|
@ -929,7 +878,7 @@ const App = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{DEV_UI_ENABLED &&
|
{IS_DEV &&
|
||||||
serverHealthy &&
|
serverHealthy &&
|
||||||
(skipToolsCheck ||
|
(skipToolsCheck ||
|
||||||
Boolean(gitCheckResult?.git.ok && gitCheckResult?.lfs.ok)) && (
|
Boolean(gitCheckResult?.git.ok && gitCheckResult?.lfs.ok)) && (
|
||||||
|
|
@ -945,7 +894,7 @@ const App = () => {
|
||||||
다음으로 이동
|
다음으로 이동
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{DEV_UI_ENABLED && (
|
{IS_DEV && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="ghost"
|
className="ghost"
|
||||||
|
|
@ -1001,6 +950,22 @@ const App = () => {
|
||||||
>
|
>
|
||||||
비밀번호 변경
|
비밀번호 변경
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="ghost"
|
||||||
|
onClick={() => {
|
||||||
|
const fallbackId = loginId.trim() || "demo";
|
||||||
|
setProfile({
|
||||||
|
username: fallbackId,
|
||||||
|
nickname: "Demo",
|
||||||
|
level: 1
|
||||||
|
});
|
||||||
|
setHasSession(true);
|
||||||
|
setScreen("main");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
유효 세션 가정(데모)
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -140,16 +140,6 @@ interface Window {
|
||||||
error?: string;
|
error?: string;
|
||||||
url: string;
|
url: string;
|
||||||
}>;
|
}>;
|
||||||
resumeSession: () => Promise<{
|
|
||||||
ok: boolean;
|
|
||||||
status?: number;
|
|
||||||
data?: unknown;
|
|
||||||
error?: string;
|
|
||||||
url: string;
|
|
||||||
}>;
|
|
||||||
clearSession: () => Promise<{
|
|
||||||
ok: boolean;
|
|
||||||
}>;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ export const APP_CONFIG = {
|
||||||
serverHealthcheckPath: "/launcher/ping",
|
serverHealthcheckPath: "/launcher/ping",
|
||||||
serverHealthcheckTimeoutMs: 2000,
|
serverHealthcheckTimeoutMs: 2000,
|
||||||
serverRequestTimeoutMs: 4000,
|
serverRequestTimeoutMs: 4000,
|
||||||
sessionTtlMs: 7 * 24 * 60 * 60 * 1000,
|
|
||||||
commandTimeoutMs: 4000,
|
commandTimeoutMs: 4000,
|
||||||
installTimeoutMs: 10 * 60 * 1000,
|
installTimeoutMs: 10 * 60 * 1000,
|
||||||
healthcheckIntervalMs: 10000,
|
healthcheckIntervalMs: 10000,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue