Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4de7ebec17 | ||
|
|
d43dff35fb | ||
|
|
5b13f8bd30 | ||
|
|
e51bc6f711 | ||
|
|
1b08aa0c58 | ||
|
|
f2547fe207 | ||
|
|
cec7726731 | ||
|
|
d679a6fed8 |
+1
-1
@@ -1047,7 +1047,7 @@ function increaseRefresh($type = "", $cnt = 1)
|
|||||||
|
|
||||||
$logDB = FileDB::db($logPath, $serverPath . '/../f_install/sql/api_log.sql');
|
$logDB = FileDB::db($logPath, $serverPath . '/../f_install/sql/api_log.sql');
|
||||||
|
|
||||||
$ip = $_SERVER['REMOTE_ADDR'] ?? 'local';
|
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? 'local';
|
||||||
$date = date('Y-m-d H:i:s');
|
$date = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
$logDB->insert('api_log', [
|
$logDB->insert('api_log', [
|
||||||
|
|||||||
@@ -3134,7 +3134,7 @@ class GeneralAI
|
|||||||
$rawCity = $db->queryFirstRow('SELECT * FROM city WHERE city = %i', $this->general->getCityID());
|
$rawCity = $db->queryFirstRow('SELECT * FROM city WHERE city = %i', $this->general->getCityID());
|
||||||
$this->general->setRawCity($rawCity);
|
$this->general->setRawCity($rawCity);
|
||||||
}
|
}
|
||||||
if (!in_array($this->general->getRawCity()['level'], [5, 6])) {
|
if (in_array($this->general->getRawCity()['level'], [5, 6])) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -345,7 +345,7 @@ class InheritancePointManager
|
|||||||
'general_id'=>$general->getID(),
|
'general_id'=>$general->getID(),
|
||||||
'year'=>$year,
|
'year'=>$year,
|
||||||
'month'=>$month,
|
'month'=>$month,
|
||||||
'value'=>$inheritStor->getAll(true)
|
'value'=>Json::encode($inheritStor->getAll(true))
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
{
|
||||||
|
"title":"【공백지】 요양 ",
|
||||||
|
"startYear":180,
|
||||||
|
"map":{
|
||||||
|
"mapName":"miniche"
|
||||||
|
},
|
||||||
|
"history":[
|
||||||
|
"<C>●</>180년 1월:<L><b>【이벤트】</b></>쉬었다 가는 깃수입니다."
|
||||||
|
],
|
||||||
|
"const": {
|
||||||
|
"joinRuinedNPCProp":0,
|
||||||
|
"npcBanMessageProb":0.005,
|
||||||
|
"defaultMaxGeneral": 800,
|
||||||
|
"allItems":{
|
||||||
|
"horse":{
|
||||||
|
"che_명마_01_노기":0, "che_명마_02_조랑":0, "che_명마_03_노새":0,
|
||||||
|
"che_명마_04_나귀":0, "che_명마_05_갈색마":0, "che_명마_06_흑색마":0,
|
||||||
|
|
||||||
|
"che_명마_07_백마" : 2, "che_명마_07_기주마" : 2, "che_명마_07_오환마" : 2, "che_명마_07_백상" : 2,
|
||||||
|
"che_명마_08_양주마" : 2, "che_명마_08_흉노마" : 2, "che_명마_09_과하마" : 2, "che_명마_09_의남백마" : 2,
|
||||||
|
"che_명마_10_대완마" : 2, "che_명마_10_옥추마" : 2, "che_명마_11_서량마" : 2, "che_명마_11_화종마" : 2,
|
||||||
|
"che_명마_12_사륜거" : 2, "che_명마_12_옥란백용구": 2, "che_명마_13_절영" : 2, "che_명마_13_적로" : 2,
|
||||||
|
"che_명마_14_적란마" : 2, "che_명마_14_조황비전" : 2, "che_명마_15_한혈마" : 2, "che_명마_15_적토마" : 2
|
||||||
|
},
|
||||||
|
"weapon":{
|
||||||
|
"che_무기_01_단도":0, "che_무기_02_단궁":0, "che_무기_03_단극":0,
|
||||||
|
"che_무기_04_목검":0, "che_무기_05_죽창":0, "che_무기_06_소부":0,
|
||||||
|
|
||||||
|
"che_무기_07_동추":2, "che_무기_07_철편":2, "che_무기_07_철쇄":2, "che_무기_07_맥궁":2,
|
||||||
|
"che_무기_08_유성추":2, "che_무기_08_철질여골":2, "che_무기_09_쌍철극":2, "che_무기_09_동호비궁":2,
|
||||||
|
"che_무기_10_삼첨도":2, "che_무기_10_대부":2, "che_무기_11_고정도":2, "che_무기_11_이광궁":2,
|
||||||
|
"che_무기_12_철척사모":2, "che_무기_12_칠성검":2, "che_무기_13_사모":2, "che_무기_13_양유기궁":2,
|
||||||
|
"che_무기_14_언월도":2, "che_무기_14_방천화극":2, "che_무기_15_청홍검":2, "che_무기_15_의천검":2
|
||||||
|
},
|
||||||
|
"book":{
|
||||||
|
"che_서적_01_효경전":0, "che_서적_02_회남자":0, "che_서적_03_변도론":0,
|
||||||
|
"che_서적_04_건상역주":0, "che_서적_05_여씨춘추":0, "che_서적_06_사민월령":0,
|
||||||
|
|
||||||
|
"che_서적_07_위료자":2, "che_서적_07_사마법":2, "che_서적_07_한서":2, "che_서적_07_논어":2,
|
||||||
|
"che_서적_08_전론":2, "che_서적_08_사기":2, "che_서적_09_장자":2, "che_서적_09_역경":2,
|
||||||
|
"che_서적_10_시경":2, "che_서적_10_구국론":2, "che_서적_11_상군서":2, "che_서적_11_춘추전":2,
|
||||||
|
"che_서적_12_산해경":2, "che_서적_12_맹덕신서":2, "che_서적_13_관자":2, "che_서적_13_병법24편":2,
|
||||||
|
"che_서적_14_한비자":2, "che_서적_14_오자병법":2, "che_서적_15_노자":2, "che_서적_15_손자병법":2
|
||||||
|
},
|
||||||
|
"item":{
|
||||||
|
"che_치료_환약":0, "che_저격_수극":0, "che_사기_탁주":0,
|
||||||
|
"che_훈련_청주":0, "che_계략_이추":0, "che_계략_향낭":0,
|
||||||
|
|
||||||
|
"event_전투특기_격노":0, "event_전투특기_견고":0, "event_전투특기_공성":0, "event_전투특기_궁병":0,
|
||||||
|
"event_전투특기_귀병":0, "event_전투특기_기병":0, "event_전투특기_돌격":0, "event_전투특기_무쌍":0,
|
||||||
|
"event_전투특기_반계":0, "event_전투특기_보병":0, "event_전투특기_신산":0, "event_전투특기_신중":0,
|
||||||
|
"event_전투특기_위압":0, "event_전투특기_의술":0, "event_전투특기_저격":0, "event_전투특기_집중":0,
|
||||||
|
"event_전투특기_징병":0, "event_전투특기_척사":0, "event_전투특기_필살":0, "event_전투특기_환술":0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"events":[
|
||||||
|
[
|
||||||
|
"month", 1000,
|
||||||
|
["or", ["Date", "==", null, 12], ["Date", "==", null, 6]],
|
||||||
|
["CreateManyNPC", 15, 10],
|
||||||
|
["DeleteEvent"]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"month", 1000,
|
||||||
|
["Date", "==", 181, 1],
|
||||||
|
["RaiseNPCNation"],
|
||||||
|
["DeleteEvent"]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"month", 999,
|
||||||
|
["Date", "==", 181, 1],
|
||||||
|
["OpenNationBetting", 4, 5000],
|
||||||
|
["OpenNationBetting", 1, 2000],
|
||||||
|
["DeleteEvent"]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"month", 1000,
|
||||||
|
["Date", "==", null, 1],
|
||||||
|
["ChangeCity", "all", {
|
||||||
|
"trade":100
|
||||||
|
}]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"month", 1000,
|
||||||
|
["Date", "==", null, 7],
|
||||||
|
["ChangeCity", "all", {
|
||||||
|
"trade":100
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { AutoLoginFailed, AutoLoginNonceResponse, AutoLoginResponse, LoginFailed, LoginResponse } from "./defs/API/Login";
|
import type { AutoLoginFailed, AutoLoginNonceResponse, AutoLoginResponse, LoginFailed, LoginResponse } from "./defs/API/Login";
|
||||||
import { APIPathGen } from "./util/APIPathGen";
|
import { APIPathGen } from "./util/APIPathGen";
|
||||||
import { callSammoAPI, extractHttpMethod, GET, POST, type APICallT, type APITail, type InvalidResponse, type RawArgType, type ValidResponse } from "./util/callSammoAPI";
|
import { callSammoAPI, extractHttpMethod, POST, type APICallT, type APITail, type InvalidResponse, type RawArgType, type ValidResponse } from "./util/callSammoAPI";
|
||||||
export type { ValidResponse, InvalidResponse };
|
export type { ValidResponse, InvalidResponse };
|
||||||
|
|
||||||
const apiRealPath = {
|
const apiRealPath = {
|
||||||
@@ -18,7 +18,7 @@ const apiRealPath = {
|
|||||||
hashedToken: string,
|
hashedToken: string,
|
||||||
token_id: number,
|
token_id: number,
|
||||||
}, AutoLoginResponse, AutoLoginFailed>,
|
}, AutoLoginResponse, AutoLoginFailed>,
|
||||||
ReqNonce: GET as APICallT<undefined, AutoLoginNonceResponse, AutoLoginFailed>
|
ReqNonce: POST as APICallT<undefined, AutoLoginNonceResponse, AutoLoginFailed>
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@@ -30,4 +30,4 @@ export const SammoRootAPI = APIPathGen(apiRealPath, (path: string[], tail: APITa
|
|||||||
}
|
}
|
||||||
return callSammoAPI(method, path.join('/'), args, pathParam);
|
return callSammoAPI(method, path.join('/'), args, pathParam);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
+70
-55
@@ -37,14 +37,16 @@ function regNextToken(tokenInfo: [number, string]) {
|
|||||||
function getToken(): [number, string] | undefined {
|
function getToken(): [number, string] | undefined {
|
||||||
const trialToken = localStorage.getItem(LOGIN_TOKEN_KEY);
|
const trialToken = localStorage.getItem(LOGIN_TOKEN_KEY);
|
||||||
if (!trialToken) {
|
if (!trialToken) {
|
||||||
|
console.log('no token');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const tokenItems = JSON.parse(trialToken) as [number, [number, string], string];
|
const tokenItems = JSON.parse(trialToken) as [number, [number, string], number];
|
||||||
if (tokenItems[0] != TOKEN_VERSION) {
|
if (tokenItems[0] != TOKEN_VERSION) {
|
||||||
console.log(tokenItems);
|
console.log(tokenItems);
|
||||||
resetToken();
|
resetToken();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
console.debug(localStorage.getItem(LOGIN_TOKEN_KEY));
|
||||||
const [, token,] = tokenItems;
|
const [, token,] = tokenItems;
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
@@ -53,60 +55,73 @@ function resetToken() {
|
|||||||
localStorage.removeItem(LOGIN_TOKEN_KEY);
|
localStorage.removeItem(LOGIN_TOKEN_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function tryAutoLogin() {
|
async function tryAutoLogin() {
|
||||||
try {
|
try {
|
||||||
const tokenInfo = getToken();
|
const tokenInfo = getToken();
|
||||||
if (!tokenInfo) {
|
if (!tokenInfo) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [tokenID, token] = tokenInfo;
|
const [tokenID, token] = tokenInfo;
|
||||||
|
|
||||||
const result = await SammoRootAPI.Login.ReqNonce(undefined, true);
|
for (let attempt = 0; attempt < 2; attempt++) {
|
||||||
|
const reqNonceStartAt = Date.now();
|
||||||
if (!result) {
|
const nonceResult = await SammoRootAPI.Login.ReqNonce(undefined, true);
|
||||||
//api 에러.
|
|
||||||
return;
|
if (!nonceResult) {
|
||||||
}
|
//api 에러.
|
||||||
|
return;
|
||||||
if (!result.result) {
|
}
|
||||||
resetToken();
|
|
||||||
return;
|
if (!nonceResult.result) {
|
||||||
}
|
resetToken();
|
||||||
|
return;
|
||||||
const nonce = result.loginNonce;
|
}
|
||||||
|
|
||||||
const hashedToken = sha512(token + nonce);
|
const nonce = nonceResult.loginNonce;
|
||||||
const loginResult = await SammoRootAPI.Login.LoginByToken({
|
console.debug(
|
||||||
'hashedToken': hashedToken,
|
'try auto login with token',
|
||||||
'token_id': tokenID,
|
tokenID,
|
||||||
}, true);
|
`attempt:${attempt + 1}`,
|
||||||
|
`reqNonceElapsed:${Date.now() - reqNonceStartAt}ms`
|
||||||
if (!loginResult.result) {
|
);
|
||||||
if (!loginResult.silent) {
|
|
||||||
alert(loginResult.reason);
|
const hashedToken = sha512(token + nonce);
|
||||||
}
|
const loginResult = await SammoRootAPI.Login.LoginByToken({
|
||||||
console.error(loginResult.reason);
|
'hashedToken': hashedToken,
|
||||||
return;
|
'token_id': tokenID,
|
||||||
}
|
}, true);
|
||||||
|
|
||||||
if (loginResult.nextToken) {
|
if (!loginResult.result) {
|
||||||
regNextToken(loginResult.nextToken);
|
if (loginResult.reason === '자동 로그인: 절차 오류' && attempt === 0) {
|
||||||
}
|
console.warn('auto login failed by procedure error. retrying once.');
|
||||||
window.location.href = "./";
|
await delay(150);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
catch (e) {
|
|
||||||
if (isString(e)) {
|
if (!loginResult.silent) {
|
||||||
alert(e);
|
alert(loginResult.reason);
|
||||||
}
|
}
|
||||||
console.error(e);
|
console.error(loginResult.reason);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (loginResult.nextToken) {
|
||||||
}
|
regNextToken(loginResult.nextToken);
|
||||||
|
}
|
||||||
|
window.location.href = "./";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
if (isString(e)) {
|
||||||
|
alert(e);
|
||||||
|
}
|
||||||
|
console.error(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getOAuthToken(mode: string, scope_list?: string[] | string) {
|
function getOAuthToken(mode: string, scope_list?: string[] | string) {
|
||||||
if (mode === undefined) {
|
if (mode === undefined) {
|
||||||
mode = 'login';
|
mode = 'login';
|
||||||
|
|||||||
+73
-8
@@ -36,6 +36,57 @@ type ProcResult = {
|
|||||||
lastExecuted?: string;
|
lastExecuted?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class CookieJar {
|
||||||
|
#cookies = new Map<string, string>();
|
||||||
|
|
||||||
|
getCookieHeader(): string | undefined {
|
||||||
|
if (this.#cookies.size === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return [...this.#cookies.entries()].map(([key, value]) => `${key}=${value}`).join("; ");
|
||||||
|
}
|
||||||
|
|
||||||
|
ingestSetCookie(setCookieHeaders: string[]): void {
|
||||||
|
for (const setCookie of setCookieHeaders) {
|
||||||
|
const cookiePart = setCookie.split(";", 1)[0]?.trim();
|
||||||
|
if (!cookiePart) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const eqPos = cookiePart.indexOf("=");
|
||||||
|
if (eqPos <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = cookiePart.slice(0, eqPos).trim();
|
||||||
|
const value = cookiePart.slice(eqPos + 1).trim();
|
||||||
|
|
||||||
|
if (!key) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === "") {
|
||||||
|
this.#cookies.delete(key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this.#cookies.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSetCookieHeaders(headers: Headers): string[] {
|
||||||
|
const headerObj = headers as unknown as { getSetCookie?: () => string[] };
|
||||||
|
if (typeof headerObj.getSetCookie === "function") {
|
||||||
|
return headerObj.getSetCookie();
|
||||||
|
}
|
||||||
|
|
||||||
|
const fallback = headers.get("set-cookie");
|
||||||
|
if (!fallback) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [fallback];
|
||||||
|
}
|
||||||
|
|
||||||
function nowStr() {
|
function nowStr() {
|
||||||
const d = new Date();
|
const d = new Date();
|
||||||
const pad = (n: number) => n.toString().padStart(2, "0");
|
const pad = (n: number) => n.toString().padStart(2, "0");
|
||||||
@@ -131,13 +182,19 @@ async function scanOnce(): Promise<ServerEntry[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============ 공개 접근 체크 ============
|
// ============ 공개 접근 체크 ============
|
||||||
async function isPublicReachable(signal?: AbortSignal): Promise<boolean> {
|
async function isPublicReachable(signal?: AbortSignal, cookieJar?: CookieJar): Promise<boolean> {
|
||||||
if (!REQUIRE_PUBLIC) return true;
|
if (!REQUIRE_PUBLIC) return true;
|
||||||
const ctrl = new AbortController();
|
const ctrl = new AbortController();
|
||||||
const timer = setTimeout(() => ctrl.abort(), Math.min(5_000, FETCH_TIMEOUT_MS));
|
const timer = setTimeout(() => ctrl.abort(), Math.min(5_000, FETCH_TIMEOUT_MS));
|
||||||
const composite = anySignal([ctrl.signal, signal].filter(Boolean) as AbortSignal[]);
|
const composite = anySignal([ctrl.signal, signal].filter(Boolean) as AbortSignal[]);
|
||||||
try {
|
try {
|
||||||
const res = await fetch(webBase, { method: "GET", signal: composite });
|
const cookieHeader = cookieJar?.getCookieHeader();
|
||||||
|
const res = await fetch(webBase, {
|
||||||
|
method: "GET",
|
||||||
|
signal: composite,
|
||||||
|
headers: cookieHeader ? { cookie: cookieHeader } : undefined,
|
||||||
|
});
|
||||||
|
cookieJar?.ingestSetCookie(getSetCookieHeaders(res.headers));
|
||||||
if (!res.ok) { log(`[PUBLIC] 응답 ${res.status} → 비공개로 간주`); return false; }
|
if (!res.ok) { log(`[PUBLIC] 응답 ${res.status} → 비공개로 간주`); return false; }
|
||||||
return true;
|
return true;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@@ -150,14 +207,20 @@ async function isPublicReachable(signal?: AbortSignal): Promise<boolean> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============ HTTP ============
|
// ============ HTTP ============
|
||||||
async function httpGetJson<T extends Json>(url: string, outerSignal?: AbortSignal): Promise<T | null> {
|
async function httpGetJson<T extends Json>(url: string, outerSignal?: AbortSignal, cookieJar?: CookieJar): Promise<T | null> {
|
||||||
const ctrl = new AbortController();
|
const ctrl = new AbortController();
|
||||||
const timer = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
|
const timer = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
|
||||||
const signal = anySignal([ctrl.signal, outerSignal].filter(Boolean) as AbortSignal[]);
|
const signal = anySignal([ctrl.signal, outerSignal].filter(Boolean) as AbortSignal[]);
|
||||||
|
|
||||||
const t0 = performance.now();
|
const t0 = performance.now();
|
||||||
try {
|
try {
|
||||||
const res = await fetch(url, { signal, cache: "no-store" });
|
const cookieHeader = cookieJar?.getCookieHeader();
|
||||||
|
const res = await fetch(url, {
|
||||||
|
signal,
|
||||||
|
cache: "no-store",
|
||||||
|
headers: cookieHeader ? { cookie: cookieHeader } : undefined,
|
||||||
|
});
|
||||||
|
cookieJar?.ingestSetCookie(getSetCookieHeaders(res.headers));
|
||||||
const dt = Math.round(performance.now() - t0);
|
const dt = Math.round(performance.now() - t0);
|
||||||
if (!res.ok) { log("HTTPError:", res.status, url); return null; }
|
if (!res.ok) { log("HTTPError:", res.status, url); return null; }
|
||||||
try {
|
try {
|
||||||
@@ -195,6 +258,7 @@ class ServerRunner {
|
|||||||
#stopCtrl = new AbortController();
|
#stopCtrl = new AbortController();
|
||||||
#stopped = false;
|
#stopped = false;
|
||||||
#lastAutoResetAt = 0;
|
#lastAutoResetAt = 0;
|
||||||
|
#cookieJar = new CookieJar();
|
||||||
|
|
||||||
constructor(entry: ServerEntry) {
|
constructor(entry: ServerEntry) {
|
||||||
this.#entry = entry;
|
this.#entry = entry;
|
||||||
@@ -222,7 +286,7 @@ class ServerRunner {
|
|||||||
while (!this.#stopCtrl.signal.aborted) {
|
while (!this.#stopCtrl.signal.aborted) {
|
||||||
try {
|
try {
|
||||||
// 공개 접근 체크
|
// 공개 접근 체크
|
||||||
const pub = await isPublicReachable(this.#stopCtrl.signal);
|
const pub = await isPublicReachable(this.#stopCtrl.signal, publicCookieJar);
|
||||||
if (!pub) { await delay(15_000, undefined, { signal: this.#stopCtrl.signal }).catch(() => { /* empty */ }); continue; }
|
if (!pub) { await delay(15_000, undefined, { signal: this.#stopCtrl.signal }).catch(() => { /* empty */ }); continue; }
|
||||||
|
|
||||||
const hidden = await this.#entry.isHidden();
|
const hidden = await this.#entry.isHidden();
|
||||||
@@ -236,7 +300,7 @@ class ServerRunner {
|
|||||||
if (this.#stopCtrl.signal.aborted) break;
|
if (this.#stopCtrl.signal.aborted) break;
|
||||||
}
|
}
|
||||||
log(`[${this.name()}] 닫힘 - (정렬) autoreset 호출 @ mm:00${AUTO_RESET_JITTER_MS ? `+${AUTO_RESET_JITTER_MS}ms` : ""}`);
|
log(`[${this.name()}] 닫힘 - (정렬) autoreset 호출 @ mm:00${AUTO_RESET_JITTER_MS ? `+${AUTO_RESET_JITTER_MS}ms` : ""}`);
|
||||||
await httpGetJson<Json>(this.#entry.autoresetUrl, this.#stopCtrl.signal);
|
await httpGetJson<Json>(this.#entry.autoresetUrl, this.#stopCtrl.signal, this.#cookieJar);
|
||||||
// 같은 분에 중복 호출 방지: 0초 직후 잠깐 쉬고 다음 루프는 자연스럽게 다음 분으로 정렬됨
|
// 같은 분에 중복 호출 방지: 0초 직후 잠깐 쉬고 다음 루프는 자연스럽게 다음 분으로 정렬됨
|
||||||
await delay(200, undefined, { signal: this.#stopCtrl.signal }).catch(() => { /* empty */ });
|
await delay(200, undefined, { signal: this.#stopCtrl.signal }).catch(() => { /* empty */ });
|
||||||
continue;
|
continue;
|
||||||
@@ -244,7 +308,7 @@ class ServerRunner {
|
|||||||
|
|
||||||
|
|
||||||
// 열림: proc
|
// 열림: proc
|
||||||
const data = await httpGetJson<ProcResult>(this.#entry.procUrl, this.#stopCtrl.signal);
|
const data = await httpGetJson<ProcResult>(this.#entry.procUrl, this.#stopCtrl.signal, this.#cookieJar);
|
||||||
if (!data) {
|
if (!data) {
|
||||||
await delay(IF_LOCKED_WAIT_MS, undefined, { signal: this.#stopCtrl.signal }).catch(() => { /* empty */ });
|
await delay(IF_LOCKED_WAIT_MS, undefined, { signal: this.#stopCtrl.signal }).catch(() => { /* empty */ });
|
||||||
continue;
|
continue;
|
||||||
@@ -348,6 +412,7 @@ class DaemonManager {
|
|||||||
|
|
||||||
// ============ 메인 ============
|
// ============ 메인 ============
|
||||||
const manager = new DaemonManager();
|
const manager = new DaemonManager();
|
||||||
|
const publicCookieJar = new CookieJar();
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
// 신호 처리: SIGINT/SIGTERM → graceful shutdown
|
// 신호 처리: SIGINT/SIGTERM → graceful shutdown
|
||||||
@@ -372,4 +437,4 @@ async function main() {
|
|||||||
main().catch((e) => {
|
main().catch((e) => {
|
||||||
log("치명적 예외:", e);
|
log("치명적 예외:", e);
|
||||||
process.exitCode = 1;
|
process.exitCode = 1;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ use sammo\TimeUtil;
|
|||||||
use sammo\Util;
|
use sammo\Util;
|
||||||
use sammo\Validator;
|
use sammo\Validator;
|
||||||
|
|
||||||
|
use function sammo\logError;
|
||||||
|
|
||||||
class LoginByToken extends LoginByID
|
class LoginByToken extends LoginByID
|
||||||
{
|
{
|
||||||
static array $sensitiveArgs = ['hashedToken'];
|
static array $sensitiveArgs = ['hashedToken'];
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class APIHelper
|
|||||||
$logPath = "{$rootPath}/d_log/{$realYearMonth}_api_log.db";
|
$logPath = "{$rootPath}/d_log/{$realYearMonth}_api_log.db";
|
||||||
}
|
}
|
||||||
$logDB = FileDB::db($logPath, __DIR__ . '/../../f_install/sql/api_log.sql');
|
$logDB = FileDB::db($logPath, __DIR__ . '/../../f_install/sql/api_log.sql');
|
||||||
$ip = $_SERVER['REMOTE_ADDR'] ?? 'local';
|
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? 'local';
|
||||||
$date = date('Y-m-d H:i:s');
|
$date = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
//NOTE: array_merge([], {})의 상황이 가능함.
|
//NOTE: array_merge([], {})의 상황이 가능함.
|
||||||
|
|||||||
+1
-19
@@ -947,31 +947,13 @@ class Util
|
|||||||
return $flattened;
|
return $flattened;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the IP address of the client.
|
|
||||||
*
|
|
||||||
* @param boolean $trust_proxy_headers Whether or not to trust the
|
|
||||||
* proxy headers HTTP_CLIENT_IP
|
|
||||||
* and HTTP_X_FORWARDED_FOR. ONLY
|
|
||||||
* use if your server is behind a
|
|
||||||
* proxy that sets these values
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public static function get_client_ip($trust_proxy_headers = false)
|
public static function get_client_ip($trust_proxy_headers = false)
|
||||||
{
|
{
|
||||||
if (!$trust_proxy_headers) {
|
if (!$trust_proxy_headers) {
|
||||||
return $_SERVER['REMOTE_ADDR'];
|
return $_SERVER['REMOTE_ADDR'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
|
return $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? 'local';
|
||||||
$ip = $_SERVER['HTTP_CLIENT_IP'];
|
|
||||||
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
|
||||||
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
|
|
||||||
} else {
|
|
||||||
$ip = $_SERVER['REMOTE_ADDR'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $ip;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user