viewlogic 1.2.2 → 1.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +532 -857
- package/dist/viewlogic-router.js +293 -466
- package/dist/viewlogic-router.js.map +3 -3
- package/dist/viewlogic-router.min.js +3 -3
- package/dist/viewlogic-router.min.js.map +4 -4
- package/package.json +1 -1
package/dist/viewlogic-router.js
CHANGED
|
@@ -9,13 +9,8 @@ var I18nManager = class {
|
|
|
9
9
|
constructor(router, options = {}) {
|
|
10
10
|
this.config = {
|
|
11
11
|
enabled: options.useI18n !== void 0 ? options.useI18n : true,
|
|
12
|
-
defaultLanguage: options.defaultLanguage || "
|
|
13
|
-
fallbackLanguage: options.defaultLanguage || "
|
|
14
|
-
cacheKey: options.cacheKey || "viewlogic_lang",
|
|
15
|
-
dataCacheKey: options.dataCacheKey || "viewlogic_i18n_data",
|
|
16
|
-
cacheVersion: options.cacheVersion || "1.0.0",
|
|
17
|
-
enableDataCache: options.enableDataCache !== false,
|
|
18
|
-
debug: options.debug || false
|
|
12
|
+
defaultLanguage: options.defaultLanguage || "en",
|
|
13
|
+
fallbackLanguage: options.defaultLanguage || "en"
|
|
19
14
|
};
|
|
20
15
|
this.router = router;
|
|
21
16
|
this.messages = /* @__PURE__ */ new Map();
|
|
@@ -33,10 +28,6 @@ var I18nManager = class {
|
|
|
33
28
|
return;
|
|
34
29
|
}
|
|
35
30
|
this.loadLanguageFromCache();
|
|
36
|
-
if (this.config.debug) {
|
|
37
|
-
this.config.enableDataCache = false;
|
|
38
|
-
this.log("debug", "Data cache disabled in debug mode");
|
|
39
|
-
}
|
|
40
31
|
if (!this.messages.has(this.currentLanguage)) {
|
|
41
32
|
try {
|
|
42
33
|
await this.loadMessages(this.currentLanguage);
|
|
@@ -54,7 +45,7 @@ var I18nManager = class {
|
|
|
54
45
|
*/
|
|
55
46
|
loadLanguageFromCache() {
|
|
56
47
|
try {
|
|
57
|
-
const cachedLang =
|
|
48
|
+
const cachedLang = this.router.cacheManager?.get("viewlogic_lang");
|
|
58
49
|
if (cachedLang && this.isValidLanguage(cachedLang)) {
|
|
59
50
|
this.currentLanguage = cachedLang;
|
|
60
51
|
this.log("debug", "Language loaded from cache:", cachedLang);
|
|
@@ -118,7 +109,7 @@ var I18nManager = class {
|
|
|
118
109
|
*/
|
|
119
110
|
saveLanguageToCache(language) {
|
|
120
111
|
try {
|
|
121
|
-
|
|
112
|
+
this.router.cacheManager?.set("viewlogic_lang", language);
|
|
122
113
|
this.log("debug", "Language saved to cache:", language);
|
|
123
114
|
} catch (error) {
|
|
124
115
|
this.log("warn", "Failed to save language to cache:", error);
|
|
@@ -156,12 +147,11 @@ var I18nManager = class {
|
|
|
156
147
|
* 파일에서 메시지 로드 (캐싱 지원)
|
|
157
148
|
*/
|
|
158
149
|
async _loadMessagesFromFile(language) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
150
|
+
const cacheKey = `i18n_${language}`;
|
|
151
|
+
const cachedData = this.router.cacheManager?.get(cacheKey);
|
|
152
|
+
if (cachedData) {
|
|
153
|
+
this.log("debug", "Messages loaded from cache:", language);
|
|
154
|
+
return cachedData;
|
|
165
155
|
}
|
|
166
156
|
try {
|
|
167
157
|
const i18nPath = `${this.router.config.i18nPath}/${language}.json`;
|
|
@@ -170,9 +160,7 @@ var I18nManager = class {
|
|
|
170
160
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
171
161
|
}
|
|
172
162
|
const messages = await response.json();
|
|
173
|
-
|
|
174
|
-
this.saveDataToCache(language, messages);
|
|
175
|
-
}
|
|
163
|
+
this.router.cacheManager?.set(cacheKey, messages);
|
|
176
164
|
return messages;
|
|
177
165
|
} catch (error) {
|
|
178
166
|
this.log("error", "Failed to load messages file for:", language, error);
|
|
@@ -189,51 +177,6 @@ var I18nManager = class {
|
|
|
189
177
|
return {};
|
|
190
178
|
}
|
|
191
179
|
}
|
|
192
|
-
/**
|
|
193
|
-
* 언어 데이터를 캐시에서 가져오기
|
|
194
|
-
*/
|
|
195
|
-
getDataFromCache(language) {
|
|
196
|
-
try {
|
|
197
|
-
const cacheKey = `${this.config.dataCacheKey}_${language}_${this.config.cacheVersion}`;
|
|
198
|
-
const cachedItem = localStorage.getItem(cacheKey);
|
|
199
|
-
if (cachedItem) {
|
|
200
|
-
const { data, timestamp, version } = JSON.parse(cachedItem);
|
|
201
|
-
if (version !== this.config.cacheVersion) {
|
|
202
|
-
this.log("debug", "Cache version mismatch, clearing:", language);
|
|
203
|
-
localStorage.removeItem(cacheKey);
|
|
204
|
-
return null;
|
|
205
|
-
}
|
|
206
|
-
const now = Date.now();
|
|
207
|
-
const maxAge = 24 * 60 * 60 * 1e3;
|
|
208
|
-
if (now - timestamp > maxAge) {
|
|
209
|
-
this.log("debug", "Cache expired, removing:", language);
|
|
210
|
-
localStorage.removeItem(cacheKey);
|
|
211
|
-
return null;
|
|
212
|
-
}
|
|
213
|
-
return data;
|
|
214
|
-
}
|
|
215
|
-
} catch (error) {
|
|
216
|
-
this.log("warn", "Failed to read from cache:", error);
|
|
217
|
-
}
|
|
218
|
-
return null;
|
|
219
|
-
}
|
|
220
|
-
/**
|
|
221
|
-
* 언어 데이터를 캐시에 저장
|
|
222
|
-
*/
|
|
223
|
-
saveDataToCache(language, data) {
|
|
224
|
-
try {
|
|
225
|
-
const cacheKey = `${this.config.dataCacheKey}_${language}_${this.config.cacheVersion}`;
|
|
226
|
-
const cacheItem = {
|
|
227
|
-
data,
|
|
228
|
-
timestamp: Date.now(),
|
|
229
|
-
version: this.config.cacheVersion
|
|
230
|
-
};
|
|
231
|
-
localStorage.setItem(cacheKey, JSON.stringify(cacheItem));
|
|
232
|
-
this.log("debug", "Data saved to cache:", language);
|
|
233
|
-
} catch (error) {
|
|
234
|
-
this.log("warn", "Failed to save to cache:", error);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
180
|
/**
|
|
238
181
|
* 메시지 번역
|
|
239
182
|
*/
|
|
@@ -286,12 +229,6 @@ var I18nManager = class {
|
|
|
286
229
|
const pluralKey = count === 1 ? `${key}.singular` : `${key}.plural`;
|
|
287
230
|
return this.t(pluralKey, { ...params, count });
|
|
288
231
|
}
|
|
289
|
-
/**
|
|
290
|
-
* 사용 가능한 언어 목록
|
|
291
|
-
*/
|
|
292
|
-
getAvailableLanguages() {
|
|
293
|
-
return ["ko", "en"];
|
|
294
|
-
}
|
|
295
232
|
/**
|
|
296
233
|
* 언어 변경 이벤트 리스너 등록
|
|
297
234
|
*/
|
|
@@ -376,49 +313,16 @@ var I18nManager = class {
|
|
|
376
313
|
}
|
|
377
314
|
}
|
|
378
315
|
/**
|
|
379
|
-
* 캐시 초기화
|
|
316
|
+
* 캐시 초기화
|
|
380
317
|
*/
|
|
381
318
|
clearCache() {
|
|
382
319
|
try {
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
cacheKeys.forEach((key) => {
|
|
386
|
-
localStorage.removeItem(key);
|
|
387
|
-
});
|
|
388
|
-
this.log("debug", "Cache cleared, removed", cacheKeys.length, "items");
|
|
320
|
+
const clearedCount = this.router.cacheManager?.deleteByPattern("i18n_");
|
|
321
|
+
this.log("debug", "Cache cleared, removed", clearedCount, "items");
|
|
389
322
|
} catch (error) {
|
|
390
323
|
this.log("warn", "Failed to clear cache:", error);
|
|
391
324
|
}
|
|
392
325
|
}
|
|
393
|
-
/**
|
|
394
|
-
* 캐시 상태 확인
|
|
395
|
-
*/
|
|
396
|
-
getCacheInfo() {
|
|
397
|
-
const info = {
|
|
398
|
-
enabled: this.config.enableDataCache,
|
|
399
|
-
version: this.config.cacheVersion,
|
|
400
|
-
languages: {}
|
|
401
|
-
};
|
|
402
|
-
try {
|
|
403
|
-
const keys = Object.keys(localStorage);
|
|
404
|
-
const cacheKeys = keys.filter((key) => key.startsWith(this.config.dataCacheKey));
|
|
405
|
-
cacheKeys.forEach((key) => {
|
|
406
|
-
const match = key.match(new RegExp(`${this.config.dataCacheKey}_(w+)_(.+)`));
|
|
407
|
-
if (match) {
|
|
408
|
-
const [, language, version] = match;
|
|
409
|
-
const cachedItem = JSON.parse(localStorage.getItem(key));
|
|
410
|
-
info.languages[language] = {
|
|
411
|
-
version,
|
|
412
|
-
timestamp: cachedItem.timestamp,
|
|
413
|
-
age: Date.now() - cachedItem.timestamp
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
});
|
|
417
|
-
} catch (error) {
|
|
418
|
-
this.log("warn", "Failed to get cache info:", error);
|
|
419
|
-
}
|
|
420
|
-
return info;
|
|
421
|
-
}
|
|
422
326
|
/**
|
|
423
327
|
* 시스템 초기화 (현재 언어의 메시지 로드)
|
|
424
328
|
*/
|
|
@@ -450,13 +354,9 @@ var AuthManager = class {
|
|
|
450
354
|
publicRoutes: options.publicRoutes || ["login", "register", "home"],
|
|
451
355
|
checkAuthFunction: options.checkAuthFunction || null,
|
|
452
356
|
redirectAfterLogin: options.redirectAfterLogin || "home",
|
|
453
|
-
// 쿠키/스토리지 설정
|
|
454
357
|
authCookieName: options.authCookieName || "authToken",
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
authCookieOptions: options.authCookieOptions || {},
|
|
458
|
-
authSkipValidation: options.authSkipValidation || false,
|
|
459
|
-
debug: options.debug || false
|
|
358
|
+
authStorage: options.authStorage || "localStorage",
|
|
359
|
+
authSkipValidation: options.authSkipValidation || false
|
|
460
360
|
};
|
|
461
361
|
this.router = router;
|
|
462
362
|
this.eventListeners = /* @__PURE__ */ new Map();
|
|
@@ -487,7 +387,12 @@ var AuthManager = class {
|
|
|
487
387
|
}
|
|
488
388
|
if (typeof this.config.checkAuthFunction === "function") {
|
|
489
389
|
try {
|
|
490
|
-
const
|
|
390
|
+
const route = {
|
|
391
|
+
name: routeName,
|
|
392
|
+
$api: this.router.routeLoader.apiHandler.bindToComponent({}),
|
|
393
|
+
$state: this.router.stateHandler
|
|
394
|
+
};
|
|
395
|
+
const isAuthenticated2 = await this.config.checkAuthFunction(route);
|
|
491
396
|
return {
|
|
492
397
|
allowed: isAuthenticated2,
|
|
493
398
|
reason: isAuthenticated2 ? "custom_auth_success" : "custom_auth_failed",
|
|
@@ -498,7 +403,7 @@ var AuthManager = class {
|
|
|
498
403
|
return { allowed: false, reason: "custom_auth_error", error };
|
|
499
404
|
}
|
|
500
405
|
}
|
|
501
|
-
const isAuthenticated = this.
|
|
406
|
+
const isAuthenticated = this.isAuthenticated();
|
|
502
407
|
return {
|
|
503
408
|
allowed: isAuthenticated,
|
|
504
409
|
reason: isAuthenticated ? "authenticated" : "not_authenticated",
|
|
@@ -506,56 +411,40 @@ var AuthManager = class {
|
|
|
506
411
|
};
|
|
507
412
|
}
|
|
508
413
|
/**
|
|
509
|
-
*
|
|
414
|
+
* JWT 토큰 검증
|
|
510
415
|
*/
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
if (
|
|
517
|
-
|
|
518
|
-
if (payload.exp && Date.now() >= payload.exp * 1e3) {
|
|
519
|
-
this.log("debug", "localStorage token expired, removing...");
|
|
520
|
-
localStorage.removeItem("authToken");
|
|
521
|
-
localStorage.removeItem("accessToken");
|
|
522
|
-
return false;
|
|
523
|
-
}
|
|
416
|
+
isTokenValid(token) {
|
|
417
|
+
if (!token) return false;
|
|
418
|
+
try {
|
|
419
|
+
if (token.includes(".")) {
|
|
420
|
+
const payload = JSON.parse(atob(token.split(".")[1]));
|
|
421
|
+
if (payload.exp && Date.now() >= payload.exp * 1e3) {
|
|
422
|
+
return false;
|
|
524
423
|
}
|
|
525
|
-
this.log("debug", "\u2705 Valid token found in localStorage");
|
|
526
|
-
return true;
|
|
527
|
-
} catch (error) {
|
|
528
|
-
this.log("warn", "Invalid token in localStorage:", error);
|
|
529
424
|
}
|
|
530
|
-
}
|
|
531
|
-
const sessionToken = sessionStorage.getItem("authToken") || sessionStorage.getItem("accessToken");
|
|
532
|
-
if (sessionToken) {
|
|
533
|
-
this.log("debug", "\u2705 Token found in sessionStorage");
|
|
534
425
|
return true;
|
|
426
|
+
} catch (error) {
|
|
427
|
+
this.log("warn", "Token validation failed:", error);
|
|
428
|
+
return false;
|
|
535
429
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
}
|
|
547
|
-
this.log("debug", "\u2705 Valid token found in cookies");
|
|
548
|
-
return true;
|
|
549
|
-
} catch (error) {
|
|
550
|
-
this.log("warn", "Cookie token validation failed:", error);
|
|
551
|
-
}
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* 사용자 인증 상태 확인
|
|
433
|
+
*/
|
|
434
|
+
isAuthenticated() {
|
|
435
|
+
this.log("debug", "\u{1F50D} Checking user authentication status");
|
|
436
|
+
const token = this.getAccessToken();
|
|
437
|
+
if (!token) {
|
|
438
|
+
this.log("debug", "\u274C No token found");
|
|
439
|
+
return false;
|
|
552
440
|
}
|
|
553
|
-
if (
|
|
554
|
-
this.log("debug", "
|
|
555
|
-
|
|
441
|
+
if (!this.isTokenValid(token)) {
|
|
442
|
+
this.log("debug", "Token expired, removing...");
|
|
443
|
+
this.removeAccessToken();
|
|
444
|
+
return false;
|
|
556
445
|
}
|
|
557
|
-
this.log("debug", "\
|
|
558
|
-
return
|
|
446
|
+
this.log("debug", "\u2705 Valid token found");
|
|
447
|
+
return true;
|
|
559
448
|
}
|
|
560
449
|
/**
|
|
561
450
|
* 공개 라우트인지 확인
|
|
@@ -581,18 +470,7 @@ var AuthManager = class {
|
|
|
581
470
|
* 인증 쿠키 가져오기
|
|
582
471
|
*/
|
|
583
472
|
getAuthCookie() {
|
|
584
|
-
|
|
585
|
-
if (primaryCookie) {
|
|
586
|
-
return primaryCookie;
|
|
587
|
-
}
|
|
588
|
-
for (const cookieName of this.config.authFallbackCookieNames) {
|
|
589
|
-
const cookieValue = this.getCookieValue(cookieName);
|
|
590
|
-
if (cookieValue) {
|
|
591
|
-
this.log("debug", `Found auth token in fallback cookie: ${cookieName}`);
|
|
592
|
-
return cookieValue;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
return null;
|
|
473
|
+
return this.getCookieValue(this.config.authCookieName);
|
|
596
474
|
}
|
|
597
475
|
/**
|
|
598
476
|
* 쿠키 값 가져오기
|
|
@@ -609,24 +487,18 @@ var AuthManager = class {
|
|
|
609
487
|
* 인증 쿠키 제거
|
|
610
488
|
*/
|
|
611
489
|
removeAuthCookie() {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
|
|
615
|
-
document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${window.location.pathname};`;
|
|
616
|
-
});
|
|
617
|
-
this.log("debug", "Auth cookies removed");
|
|
490
|
+
document.cookie = `${this.config.authCookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
|
|
491
|
+
this.log("debug", "Auth cookie removed");
|
|
618
492
|
}
|
|
619
493
|
/**
|
|
620
494
|
* 액세스 토큰 가져오기
|
|
621
495
|
*/
|
|
622
496
|
getAccessToken() {
|
|
623
|
-
let token = localStorage.getItem("authToken")
|
|
497
|
+
let token = localStorage.getItem("authToken");
|
|
624
498
|
if (token) return token;
|
|
625
|
-
token = sessionStorage.getItem("authToken")
|
|
499
|
+
token = sessionStorage.getItem("authToken");
|
|
626
500
|
if (token) return token;
|
|
627
|
-
|
|
628
|
-
if (token) return token;
|
|
629
|
-
return null;
|
|
501
|
+
return this.getAuthCookie();
|
|
630
502
|
}
|
|
631
503
|
/**
|
|
632
504
|
* 액세스 토큰 설정
|
|
@@ -642,17 +514,9 @@ var AuthManager = class {
|
|
|
642
514
|
skipValidation = this.config.authSkipValidation
|
|
643
515
|
} = options;
|
|
644
516
|
try {
|
|
645
|
-
if (!skipValidation &&
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
if (payload.exp && Date.now() >= payload.exp * 1e3) {
|
|
649
|
-
this.log("warn", "\u274C Token is expired");
|
|
650
|
-
return false;
|
|
651
|
-
}
|
|
652
|
-
this.log("debug", "\u2705 JWT token validated");
|
|
653
|
-
} catch (error) {
|
|
654
|
-
this.log("warn", "\u26A0\uFE0F JWT validation failed, but proceeding:", error.message);
|
|
655
|
-
}
|
|
517
|
+
if (!skipValidation && !this.isTokenValid(token)) {
|
|
518
|
+
this.log("warn", "\u274C Token is expired or invalid");
|
|
519
|
+
return false;
|
|
656
520
|
}
|
|
657
521
|
switch (storage) {
|
|
658
522
|
case "localStorage":
|
|
@@ -664,7 +528,7 @@ var AuthManager = class {
|
|
|
664
528
|
this.log("debug", "Token saved to sessionStorage");
|
|
665
529
|
break;
|
|
666
530
|
case "cookie":
|
|
667
|
-
this.setAuthCookie(token
|
|
531
|
+
this.setAuthCookie(token);
|
|
668
532
|
break;
|
|
669
533
|
default:
|
|
670
534
|
localStorage.setItem("authToken", token);
|
|
@@ -684,41 +548,25 @@ var AuthManager = class {
|
|
|
684
548
|
/**
|
|
685
549
|
* 인증 쿠키 설정
|
|
686
550
|
*/
|
|
687
|
-
setAuthCookie(token
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
secure = window.location.protocol === "https:",
|
|
691
|
-
sameSite = "Strict",
|
|
692
|
-
path = "/",
|
|
693
|
-
domain = null
|
|
694
|
-
} = options;
|
|
695
|
-
let cookieString = `${cookieName}=${encodeURIComponent(token)}; path=${path}`;
|
|
551
|
+
setAuthCookie(token) {
|
|
552
|
+
const secure = window.location.protocol === "https:";
|
|
553
|
+
let cookieString = `${this.config.authCookieName}=${encodeURIComponent(token)}; path=/; SameSite=Strict`;
|
|
696
554
|
if (secure) {
|
|
697
555
|
cookieString += "; Secure";
|
|
698
556
|
}
|
|
699
|
-
if (
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
try {
|
|
706
|
-
if (token.includes(".")) {
|
|
707
|
-
try {
|
|
708
|
-
const payload = JSON.parse(atob(token.split(".")[1]));
|
|
709
|
-
if (payload.exp) {
|
|
710
|
-
const expireDate = new Date(payload.exp * 1e3);
|
|
711
|
-
cookieString += `; Expires=${expireDate.toUTCString()}`;
|
|
712
|
-
}
|
|
713
|
-
} catch (error) {
|
|
714
|
-
this.log("Could not extract expiration from JWT token");
|
|
557
|
+
if (token.includes(".")) {
|
|
558
|
+
try {
|
|
559
|
+
const payload = JSON.parse(atob(token.split(".")[1]));
|
|
560
|
+
if (payload.exp) {
|
|
561
|
+
const expireDate = new Date(payload.exp * 1e3);
|
|
562
|
+
cookieString += `; Expires=${expireDate.toUTCString()}`;
|
|
715
563
|
}
|
|
564
|
+
} catch (error) {
|
|
565
|
+
this.log("warn", "Could not extract expiration from JWT token");
|
|
716
566
|
}
|
|
717
|
-
} catch (error) {
|
|
718
|
-
this.log("Token processing error:", error);
|
|
719
567
|
}
|
|
720
568
|
document.cookie = cookieString;
|
|
721
|
-
this.log(
|
|
569
|
+
this.log("debug", "Auth cookie set");
|
|
722
570
|
}
|
|
723
571
|
/**
|
|
724
572
|
* 토큰 제거
|
|
@@ -739,9 +587,7 @@ var AuthManager = class {
|
|
|
739
587
|
case "all":
|
|
740
588
|
default:
|
|
741
589
|
localStorage.removeItem("authToken");
|
|
742
|
-
localStorage.removeItem("accessToken");
|
|
743
590
|
sessionStorage.removeItem("authToken");
|
|
744
|
-
sessionStorage.removeItem("accessToken");
|
|
745
591
|
this.removeAuthCookie();
|
|
746
592
|
break;
|
|
747
593
|
}
|
|
@@ -751,7 +597,7 @@ var AuthManager = class {
|
|
|
751
597
|
/**
|
|
752
598
|
* 로그인 성공 처리
|
|
753
599
|
*/
|
|
754
|
-
|
|
600
|
+
loginSuccess(targetRoute = null) {
|
|
755
601
|
const redirectRoute = targetRoute || this.config.redirectAfterLogin;
|
|
756
602
|
this.log(`\u{1F389} Login success, redirecting to: ${redirectRoute}`);
|
|
757
603
|
this.emitAuthEvent("login_success", { targetRoute: redirectRoute });
|
|
@@ -763,11 +609,9 @@ var AuthManager = class {
|
|
|
763
609
|
/**
|
|
764
610
|
* 로그아웃 처리
|
|
765
611
|
*/
|
|
766
|
-
|
|
612
|
+
logout() {
|
|
767
613
|
this.log("\u{1F44B} Logging out user");
|
|
768
614
|
this.removeAccessToken();
|
|
769
|
-
if (window.user) window.user = null;
|
|
770
|
-
if (window.isAuthenticated) window.isAuthenticated = false;
|
|
771
615
|
this.emitAuthEvent("logout", {});
|
|
772
616
|
if (this.router && typeof this.router.navigateTo === "function") {
|
|
773
617
|
this.router.navigateTo(this.config.loginRoute);
|
|
@@ -824,7 +668,7 @@ var AuthManager = class {
|
|
|
824
668
|
getAuthStats() {
|
|
825
669
|
return {
|
|
826
670
|
enabled: this.config.enabled,
|
|
827
|
-
isAuthenticated: this.
|
|
671
|
+
isAuthenticated: this.isAuthenticated(),
|
|
828
672
|
hasToken: !!this.getAccessToken(),
|
|
829
673
|
protectedRoutesCount: this.config.protectedRoutes.length,
|
|
830
674
|
protectedPrefixesCount: this.config.protectedPrefixes.length,
|
|
@@ -850,9 +694,8 @@ var CacheManager = class {
|
|
|
850
694
|
// 'memory' 또는 'lru'
|
|
851
695
|
cacheTTL: options.cacheTTL || 3e5,
|
|
852
696
|
// 5분 (밀리초)
|
|
853
|
-
maxCacheSize: options.maxCacheSize || 50
|
|
697
|
+
maxCacheSize: options.maxCacheSize || 50
|
|
854
698
|
// LRU 캐시 최대 크기
|
|
855
|
-
debug: options.debug || false
|
|
856
699
|
};
|
|
857
700
|
this.router = router;
|
|
858
701
|
this.cache = /* @__PURE__ */ new Map();
|
|
@@ -871,7 +714,7 @@ var CacheManager = class {
|
|
|
871
714
|
/**
|
|
872
715
|
* 캐시에 값 저장
|
|
873
716
|
*/
|
|
874
|
-
|
|
717
|
+
set(key, value) {
|
|
875
718
|
const now = Date.now();
|
|
876
719
|
if (this.config.cacheMode === "lru") {
|
|
877
720
|
if (this.cache.size >= this.config.maxCacheSize && !this.cache.has(key)) {
|
|
@@ -895,7 +738,7 @@ var CacheManager = class {
|
|
|
895
738
|
/**
|
|
896
739
|
* 캐시에서 값 가져오기
|
|
897
740
|
*/
|
|
898
|
-
|
|
741
|
+
get(key) {
|
|
899
742
|
const now = Date.now();
|
|
900
743
|
const timestamp = this.cacheTimestamps.get(key);
|
|
901
744
|
if (timestamp && now - timestamp > this.config.cacheTTL) {
|
|
@@ -928,13 +771,13 @@ var CacheManager = class {
|
|
|
928
771
|
/**
|
|
929
772
|
* 캐시에 키가 있는지 확인
|
|
930
773
|
*/
|
|
931
|
-
|
|
932
|
-
return this.cache.has(key) && this.
|
|
774
|
+
has(key) {
|
|
775
|
+
return this.cache.has(key) && this.get(key) !== null;
|
|
933
776
|
}
|
|
934
777
|
/**
|
|
935
778
|
* 특정 키 패턴의 캐시 삭제
|
|
936
779
|
*/
|
|
937
|
-
|
|
780
|
+
deleteByPattern(pattern) {
|
|
938
781
|
const keysToDelete = [];
|
|
939
782
|
for (const key of this.cache.keys()) {
|
|
940
783
|
if (key.includes(pattern) || key.startsWith(pattern)) {
|
|
@@ -951,13 +794,13 @@ var CacheManager = class {
|
|
|
951
794
|
}
|
|
952
795
|
}
|
|
953
796
|
});
|
|
954
|
-
this.log("debug", `\u{1F9F9}
|
|
797
|
+
this.log("debug", `\u{1F9F9} Deleted ${keysToDelete.length} cache entries matching: ${pattern}`);
|
|
955
798
|
return keysToDelete.length;
|
|
956
799
|
}
|
|
957
800
|
/**
|
|
958
|
-
* 특정 컴포넌트 캐시
|
|
801
|
+
* 특정 컴포넌트 캐시 삭제
|
|
959
802
|
*/
|
|
960
|
-
|
|
803
|
+
deleteComponent(routeName) {
|
|
961
804
|
const patterns = [
|
|
962
805
|
`component_${routeName}`,
|
|
963
806
|
`script_${routeName}`,
|
|
@@ -967,27 +810,27 @@ var CacheManager = class {
|
|
|
967
810
|
];
|
|
968
811
|
let totalInvalidated = 0;
|
|
969
812
|
patterns.forEach((pattern) => {
|
|
970
|
-
totalInvalidated += this.
|
|
813
|
+
totalInvalidated += this.deleteByPattern(pattern);
|
|
971
814
|
});
|
|
972
|
-
this.log(`\u{1F504}
|
|
815
|
+
this.log(`\u{1F504} Deleted component cache for route: ${routeName} (${totalInvalidated} entries)`);
|
|
973
816
|
return totalInvalidated;
|
|
974
817
|
}
|
|
975
818
|
/**
|
|
976
819
|
* 모든 컴포넌트 캐시 삭제
|
|
977
820
|
*/
|
|
978
|
-
|
|
821
|
+
deleteAllComponents() {
|
|
979
822
|
const componentPatterns = ["component_", "script_", "template_", "style_", "layout_"];
|
|
980
823
|
let totalCleared = 0;
|
|
981
824
|
componentPatterns.forEach((pattern) => {
|
|
982
|
-
totalCleared += this.
|
|
825
|
+
totalCleared += this.deleteByPattern(pattern);
|
|
983
826
|
});
|
|
984
|
-
this.log(`\u{1F9FD}
|
|
827
|
+
this.log(`\u{1F9FD} Deleted all component caches (${totalCleared} entries)`);
|
|
985
828
|
return totalCleared;
|
|
986
829
|
}
|
|
987
830
|
/**
|
|
988
831
|
* 전체 캐시 삭제
|
|
989
832
|
*/
|
|
990
|
-
|
|
833
|
+
clearAll() {
|
|
991
834
|
const size = this.cache.size;
|
|
992
835
|
this.cache.clear();
|
|
993
836
|
this.cacheTimestamps.clear();
|
|
@@ -998,7 +841,7 @@ var CacheManager = class {
|
|
|
998
841
|
/**
|
|
999
842
|
* 만료된 캐시 항목들 정리
|
|
1000
843
|
*/
|
|
1001
|
-
|
|
844
|
+
cleanExpired() {
|
|
1002
845
|
const now = Date.now();
|
|
1003
846
|
const expiredKeys = [];
|
|
1004
847
|
for (const [key, timestamp] of this.cacheTimestamps.entries()) {
|
|
@@ -1024,15 +867,15 @@ var CacheManager = class {
|
|
|
1024
867
|
/**
|
|
1025
868
|
* 캐시 통계 정보
|
|
1026
869
|
*/
|
|
1027
|
-
|
|
870
|
+
getStats() {
|
|
1028
871
|
return {
|
|
1029
872
|
size: this.cache.size,
|
|
1030
873
|
maxSize: this.config.maxCacheSize,
|
|
1031
874
|
mode: this.config.cacheMode,
|
|
1032
875
|
ttl: this.config.cacheTTL,
|
|
1033
876
|
memoryUsage: this.getMemoryUsage(),
|
|
1034
|
-
hitRatio: this.
|
|
1035
|
-
categories: this.
|
|
877
|
+
hitRatio: this.getHitRate(),
|
|
878
|
+
categories: this.getStatsByCategory()
|
|
1036
879
|
};
|
|
1037
880
|
}
|
|
1038
881
|
/**
|
|
@@ -1059,14 +902,14 @@ var CacheManager = class {
|
|
|
1059
902
|
/**
|
|
1060
903
|
* 히트 비율 계산 (간단한 추정)
|
|
1061
904
|
*/
|
|
1062
|
-
|
|
905
|
+
getHitRate() {
|
|
1063
906
|
const ratio = this.cache.size > 0 ? Math.min(this.cache.size / this.config.maxCacheSize, 1) : 0;
|
|
1064
907
|
return Math.round(ratio * 100);
|
|
1065
908
|
}
|
|
1066
909
|
/**
|
|
1067
910
|
* 카테고리별 캐시 통계
|
|
1068
911
|
*/
|
|
1069
|
-
|
|
912
|
+
getStatsByCategory() {
|
|
1070
913
|
const categories = {
|
|
1071
914
|
components: 0,
|
|
1072
915
|
scripts: 0,
|
|
@@ -1088,14 +931,14 @@ var CacheManager = class {
|
|
|
1088
931
|
/**
|
|
1089
932
|
* 캐시 키 목록 반환
|
|
1090
933
|
*/
|
|
1091
|
-
|
|
934
|
+
getKeys() {
|
|
1092
935
|
return Array.from(this.cache.keys());
|
|
1093
936
|
}
|
|
1094
937
|
/**
|
|
1095
938
|
* 특정 패턴의 캐시 키들 반환
|
|
1096
939
|
*/
|
|
1097
|
-
|
|
1098
|
-
return this.
|
|
940
|
+
getKeysByPattern(pattern) {
|
|
941
|
+
return this.getKeys().filter(
|
|
1099
942
|
(key) => key.includes(pattern) || key.startsWith(pattern)
|
|
1100
943
|
);
|
|
1101
944
|
}
|
|
@@ -1107,7 +950,7 @@ var CacheManager = class {
|
|
|
1107
950
|
clearInterval(this.cleanupInterval);
|
|
1108
951
|
}
|
|
1109
952
|
this.cleanupInterval = setInterval(() => {
|
|
1110
|
-
this.
|
|
953
|
+
this.cleanExpired();
|
|
1111
954
|
}, interval);
|
|
1112
955
|
this.log(`\u{1F916} Auto cleanup started (interval: ${interval}ms)`);
|
|
1113
956
|
}
|
|
@@ -1126,27 +969,18 @@ var CacheManager = class {
|
|
|
1126
969
|
*/
|
|
1127
970
|
destroy() {
|
|
1128
971
|
this.stopAutoCleanup();
|
|
1129
|
-
this.
|
|
972
|
+
this.clearAll();
|
|
1130
973
|
this.log("debug", "CacheManager destroyed");
|
|
1131
974
|
}
|
|
1132
975
|
};
|
|
1133
976
|
|
|
1134
977
|
// src/plugins/QueryManager.js
|
|
1135
978
|
var QueryManager = class {
|
|
1136
|
-
constructor(router
|
|
1137
|
-
this.config = {
|
|
1138
|
-
enableParameterValidation: options.enableParameterValidation !== false,
|
|
1139
|
-
logSecurityWarnings: options.logSecurityWarnings !== false,
|
|
1140
|
-
maxParameterLength: options.maxParameterLength || 1e3,
|
|
1141
|
-
maxArraySize: options.maxArraySize || 100,
|
|
1142
|
-
maxParameterCount: options.maxParameterCount || 50,
|
|
1143
|
-
allowedKeyPattern: options.allowedKeyPattern || /^[a-zA-Z0-9_\-]+$/,
|
|
1144
|
-
debug: options.debug || false
|
|
1145
|
-
};
|
|
979
|
+
constructor(router) {
|
|
1146
980
|
this.router = router;
|
|
1147
981
|
this.currentQueryParams = {};
|
|
1148
982
|
this.currentRouteParams = {};
|
|
1149
|
-
this.log("
|
|
983
|
+
this.log("debug", "QueryManager initialized");
|
|
1150
984
|
}
|
|
1151
985
|
/**
|
|
1152
986
|
* 로깅 래퍼 메서드
|
|
@@ -1156,97 +990,6 @@ var QueryManager = class {
|
|
|
1156
990
|
this.router.errorHandler.log(level, "QueryManager", ...args);
|
|
1157
991
|
}
|
|
1158
992
|
}
|
|
1159
|
-
/**
|
|
1160
|
-
* 파라미터 값 sanitize (XSS, SQL Injection 방어)
|
|
1161
|
-
*/
|
|
1162
|
-
sanitizeParameter(value) {
|
|
1163
|
-
if (typeof value !== "string") return value;
|
|
1164
|
-
let sanitized = value.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "").replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, "").replace(/<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi, "").replace(/<embed\b[^<]*(?:(?!<\/embed>)<[^<]*)*<\/embed>/gi, "").replace(/<link\b[^<]*>/gi, "").replace(/<meta\b[^<]*>/gi, "").replace(/javascript:/gi, "").replace(/vbscript:/gi, "").replace(/data:/gi, "").replace(/on\w+\s*=/gi, "").replace(/expression\s*\(/gi, "").replace(/url\s*\(/gi, "");
|
|
1165
|
-
const sqlPatterns = [
|
|
1166
|
-
/(\b(union|select|insert|update|delete|drop|create|alter|exec|execute|sp_|xp_)\b)/gi,
|
|
1167
|
-
/(;|\||&|\*|%|<|>)/g,
|
|
1168
|
-
// 위험한 특수문자
|
|
1169
|
-
/(--|\/\*|\*\/)/g,
|
|
1170
|
-
// SQL 주석
|
|
1171
|
-
/(\bor\b.*\b=\b|\band\b.*\b=\b)/gi,
|
|
1172
|
-
// OR/AND 조건문
|
|
1173
|
-
/('.*'|".*")/g,
|
|
1174
|
-
// 따옴표로 둘러싸인 문자열
|
|
1175
|
-
/(\\\w+)/g
|
|
1176
|
-
// 백슬래시 이스케이프
|
|
1177
|
-
];
|
|
1178
|
-
for (const pattern of sqlPatterns) {
|
|
1179
|
-
sanitized = sanitized.replace(pattern, "");
|
|
1180
|
-
}
|
|
1181
|
-
sanitized = sanitized.replace(/[<>'"&]{2,}/g, "");
|
|
1182
|
-
if (sanitized.length > this.config.maxParameterLength) {
|
|
1183
|
-
sanitized = sanitized.substring(0, this.config.maxParameterLength);
|
|
1184
|
-
}
|
|
1185
|
-
return sanitized.trim();
|
|
1186
|
-
}
|
|
1187
|
-
/**
|
|
1188
|
-
* 파라미터 유효성 검증
|
|
1189
|
-
*/
|
|
1190
|
-
validateParameter(key, value) {
|
|
1191
|
-
if (!this.config.enableParameterValidation) {
|
|
1192
|
-
return true;
|
|
1193
|
-
}
|
|
1194
|
-
if (typeof key !== "string" || key.length === 0) {
|
|
1195
|
-
return false;
|
|
1196
|
-
}
|
|
1197
|
-
if (!this.config.allowedKeyPattern.test(key)) {
|
|
1198
|
-
if (this.config.logSecurityWarnings) {
|
|
1199
|
-
console.warn(`Invalid parameter key format: ${key}`);
|
|
1200
|
-
}
|
|
1201
|
-
return false;
|
|
1202
|
-
}
|
|
1203
|
-
if (key.length > 50) {
|
|
1204
|
-
if (this.config.logSecurityWarnings) {
|
|
1205
|
-
console.warn(`Parameter key too long: ${key}`);
|
|
1206
|
-
}
|
|
1207
|
-
return false;
|
|
1208
|
-
}
|
|
1209
|
-
if (value !== null && value !== void 0) {
|
|
1210
|
-
if (typeof value === "string") {
|
|
1211
|
-
if (value.length > this.config.maxParameterLength) {
|
|
1212
|
-
if (this.config.logSecurityWarnings) {
|
|
1213
|
-
console.warn(`Parameter value too long for key: ${key}`);
|
|
1214
|
-
}
|
|
1215
|
-
return false;
|
|
1216
|
-
}
|
|
1217
|
-
const dangerousPatterns = [
|
|
1218
|
-
/<script|<iframe|<object|<embed/gi,
|
|
1219
|
-
/javascript:|vbscript:|data:/gi,
|
|
1220
|
-
/union.*select|insert.*into|delete.*from/gi,
|
|
1221
|
-
/\.\.\//g,
|
|
1222
|
-
// 경로 탐색 공격
|
|
1223
|
-
/[<>'"&]{3,}/g
|
|
1224
|
-
// 연속된 특수문자
|
|
1225
|
-
];
|
|
1226
|
-
for (const pattern of dangerousPatterns) {
|
|
1227
|
-
if (pattern.test(value)) {
|
|
1228
|
-
if (this.config.logSecurityWarnings) {
|
|
1229
|
-
console.warn(`Dangerous pattern detected in parameter ${key}:`, value);
|
|
1230
|
-
}
|
|
1231
|
-
return false;
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
} else if (Array.isArray(value)) {
|
|
1235
|
-
if (value.length > this.config.maxArraySize) {
|
|
1236
|
-
if (this.config.logSecurityWarnings) {
|
|
1237
|
-
console.warn(`Parameter array too large for key: ${key}`);
|
|
1238
|
-
}
|
|
1239
|
-
return false;
|
|
1240
|
-
}
|
|
1241
|
-
for (const item of value) {
|
|
1242
|
-
if (!this.validateParameter(`${key}[]`, item)) {
|
|
1243
|
-
return false;
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
return true;
|
|
1249
|
-
}
|
|
1250
993
|
/**
|
|
1251
994
|
* 쿼리스트링 파싱
|
|
1252
995
|
*/
|
|
@@ -1255,55 +998,21 @@ var QueryManager = class {
|
|
|
1255
998
|
if (!queryString) return params;
|
|
1256
999
|
const pairs = queryString.split("&");
|
|
1257
1000
|
for (const pair of pairs) {
|
|
1001
|
+
const [rawKey, rawValue] = pair.split("=");
|
|
1002
|
+
if (!rawKey) continue;
|
|
1258
1003
|
try {
|
|
1259
|
-
const
|
|
1260
|
-
|
|
1261
|
-
let key, value;
|
|
1262
|
-
try {
|
|
1263
|
-
key = decodeURIComponent(rawKey);
|
|
1264
|
-
value = rawValue ? decodeURIComponent(rawValue) : "";
|
|
1265
|
-
} catch (e) {
|
|
1266
|
-
this.log("warn", "Failed to decode URI component:", pair);
|
|
1267
|
-
continue;
|
|
1268
|
-
}
|
|
1269
|
-
if (!this.validateParameter(key, value)) {
|
|
1270
|
-
this.log("warn", `Parameter rejected by security filter: ${key}`);
|
|
1271
|
-
continue;
|
|
1272
|
-
}
|
|
1273
|
-
const sanitizedValue = this.sanitizeParameter(value);
|
|
1004
|
+
const key = decodeURIComponent(rawKey);
|
|
1005
|
+
const value = rawValue ? decodeURIComponent(rawValue) : "";
|
|
1274
1006
|
if (key.endsWith("[]")) {
|
|
1275
1007
|
const arrayKey = key.slice(0, -2);
|
|
1276
|
-
if (!this.validateParameter(arrayKey, [])) {
|
|
1277
|
-
continue;
|
|
1278
|
-
}
|
|
1279
1008
|
if (!params[arrayKey]) params[arrayKey] = [];
|
|
1280
|
-
|
|
1281
|
-
params[arrayKey].push(sanitizedValue);
|
|
1282
|
-
} else {
|
|
1283
|
-
if (this.config.logSecurityWarnings) {
|
|
1284
|
-
console.warn(`Array parameter ${arrayKey} size limit exceeded`);
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1009
|
+
params[arrayKey].push(value);
|
|
1287
1010
|
} else {
|
|
1288
|
-
params[key] =
|
|
1011
|
+
params[key] = value;
|
|
1289
1012
|
}
|
|
1290
1013
|
} catch (error) {
|
|
1291
|
-
this.log("
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
const paramCount = Object.keys(params).length;
|
|
1295
|
-
if (paramCount > this.config.maxParameterCount) {
|
|
1296
|
-
if (this.config.logSecurityWarnings) {
|
|
1297
|
-
console.warn(`Too many parameters (${paramCount}). Limiting to first ${this.config.maxParameterCount}.`);
|
|
1298
|
-
}
|
|
1299
|
-
const limitedParams = {};
|
|
1300
|
-
let count = 0;
|
|
1301
|
-
for (const [key, value] of Object.entries(params)) {
|
|
1302
|
-
if (count >= this.config.maxParameterCount) break;
|
|
1303
|
-
limitedParams[key] = value;
|
|
1304
|
-
count++;
|
|
1014
|
+
this.log("warn", "Failed to decode query parameter:", pair);
|
|
1305
1015
|
}
|
|
1306
|
-
return limitedParams;
|
|
1307
1016
|
}
|
|
1308
1017
|
return params;
|
|
1309
1018
|
}
|
|
@@ -1358,36 +1067,17 @@ var QueryManager = class {
|
|
|
1358
1067
|
*/
|
|
1359
1068
|
setQueryParams(params, replace = false) {
|
|
1360
1069
|
if (!params || typeof params !== "object") {
|
|
1361
|
-
|
|
1070
|
+
this.log("warn", "Invalid parameters object provided to setQueryParams");
|
|
1362
1071
|
return;
|
|
1363
1072
|
}
|
|
1364
1073
|
const currentParams = replace ? {} : { ...this.currentQueryParams };
|
|
1365
|
-
const sanitizedParams = {};
|
|
1366
1074
|
for (const [key, value] of Object.entries(params)) {
|
|
1367
|
-
if (
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
}
|
|
1371
|
-
if (value !== void 0 && value !== null) {
|
|
1372
|
-
if (Array.isArray(value)) {
|
|
1373
|
-
sanitizedParams[key] = value.map((item) => this.sanitizeParameter(item));
|
|
1374
|
-
} else {
|
|
1375
|
-
sanitizedParams[key] = this.sanitizeParameter(value);
|
|
1376
|
-
}
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1379
|
-
Object.assign(currentParams, sanitizedParams);
|
|
1380
|
-
for (const [key, value] of Object.entries(currentParams)) {
|
|
1381
|
-
if (value === void 0 || value === null || value === "") {
|
|
1075
|
+
if (value !== void 0 && value !== null && value !== "") {
|
|
1076
|
+
currentParams[key] = value;
|
|
1077
|
+
} else {
|
|
1382
1078
|
delete currentParams[key];
|
|
1383
1079
|
}
|
|
1384
1080
|
}
|
|
1385
|
-
const paramCount = Object.keys(currentParams).length;
|
|
1386
|
-
if (paramCount > this.config.maxParameterCount) {
|
|
1387
|
-
if (this.config.logSecurityWarnings) {
|
|
1388
|
-
console.warn(`Too many parameters after update (${paramCount}). Some parameters may be dropped.`);
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
1081
|
this.currentQueryParams = currentParams;
|
|
1392
1082
|
this.updateURL();
|
|
1393
1083
|
}
|
|
@@ -1466,8 +1156,6 @@ var QueryManager = class {
|
|
|
1466
1156
|
getStats() {
|
|
1467
1157
|
return {
|
|
1468
1158
|
currentParams: Object.keys(this.currentQueryParams).length,
|
|
1469
|
-
maxAllowed: this.config.maxParameterCount,
|
|
1470
|
-
validationEnabled: this.config.enableParameterValidation,
|
|
1471
1159
|
currentQueryString: this.buildQueryString(this.currentQueryParams)
|
|
1472
1160
|
};
|
|
1473
1161
|
}
|
|
@@ -1564,9 +1252,9 @@ var FormHandler = class {
|
|
|
1564
1252
|
const form = event.target;
|
|
1565
1253
|
let action = form.getAttribute("action");
|
|
1566
1254
|
const method = form.getAttribute("method") || "POST";
|
|
1567
|
-
const successHandler = form.getAttribute("data-success
|
|
1568
|
-
const errorHandler = form.getAttribute("data-error
|
|
1569
|
-
const loadingHandler = form.getAttribute("data-loading
|
|
1255
|
+
const successHandler = form.getAttribute("data-success");
|
|
1256
|
+
const errorHandler = form.getAttribute("data-error");
|
|
1257
|
+
const loadingHandler = form.getAttribute("data-loading");
|
|
1570
1258
|
const redirectTo = form.getAttribute("data-redirect");
|
|
1571
1259
|
action = this.processActionParams(action, component);
|
|
1572
1260
|
if (!this.validateForm(form, component)) {
|
|
@@ -1938,7 +1626,7 @@ var ComponentLoader = class {
|
|
|
1938
1626
|
throw new Error("Component name must be a non-empty string");
|
|
1939
1627
|
}
|
|
1940
1628
|
const cacheKey = `component_${componentName}`;
|
|
1941
|
-
const cachedComponent = this.router?.cacheManager?.
|
|
1629
|
+
const cachedComponent = this.router?.cacheManager?.get(cacheKey);
|
|
1942
1630
|
if (cachedComponent) {
|
|
1943
1631
|
this.log("debug", `Component '${componentName}' loaded from cache`);
|
|
1944
1632
|
return cachedComponent;
|
|
@@ -1951,7 +1639,7 @@ var ComponentLoader = class {
|
|
|
1951
1639
|
try {
|
|
1952
1640
|
const component = await loadPromise;
|
|
1953
1641
|
if (component && this.router?.cacheManager) {
|
|
1954
|
-
this.router.cacheManager.
|
|
1642
|
+
this.router.cacheManager.set(cacheKey, component);
|
|
1955
1643
|
this.log("debug", `Component '${componentName}' cached successfully`);
|
|
1956
1644
|
}
|
|
1957
1645
|
return component;
|
|
@@ -2025,11 +1713,11 @@ var ComponentLoader = class {
|
|
|
2025
1713
|
async _loadProductionComponents() {
|
|
2026
1714
|
try {
|
|
2027
1715
|
const componentsPath = `${this.router?.config?.routesPath || "/routes"}/_components.js`;
|
|
2028
|
-
this.log("
|
|
1716
|
+
this.log("debug", "[PRODUCTION] Loading unified components from:", componentsPath);
|
|
2029
1717
|
const componentsModule = await import(componentsPath);
|
|
2030
1718
|
if (typeof componentsModule.registerComponents === "function") {
|
|
2031
1719
|
this.unifiedComponents = componentsModule.components || {};
|
|
2032
|
-
this.log("
|
|
1720
|
+
this.log("debug", `[PRODUCTION] Unified components loaded: ${Object.keys(this.unifiedComponents).length} components`);
|
|
2033
1721
|
return this.unifiedComponents;
|
|
2034
1722
|
} else {
|
|
2035
1723
|
throw new Error("registerComponents function not found in components module");
|
|
@@ -2047,10 +1735,10 @@ var ComponentLoader = class {
|
|
|
2047
1735
|
const namesToLoad = componentNames || [];
|
|
2048
1736
|
const components = {};
|
|
2049
1737
|
if (namesToLoad.length === 0) {
|
|
2050
|
-
this.log("
|
|
1738
|
+
this.log("debug", "[DEVELOPMENT] No components to load");
|
|
2051
1739
|
return components;
|
|
2052
1740
|
}
|
|
2053
|
-
this.log("
|
|
1741
|
+
this.log("debug", `[DEVELOPMENT] Loading individual components: ${namesToLoad.join(", ")}`);
|
|
2054
1742
|
for (const name of namesToLoad) {
|
|
2055
1743
|
try {
|
|
2056
1744
|
const component = await this.loadComponent(name);
|
|
@@ -2061,7 +1749,7 @@ var ComponentLoader = class {
|
|
|
2061
1749
|
this.log("warn", `[DEVELOPMENT] Failed to load component ${name}:`, loadError.message);
|
|
2062
1750
|
}
|
|
2063
1751
|
}
|
|
2064
|
-
this.log("
|
|
1752
|
+
this.log("debug", `[DEVELOPMENT] Individual components loaded: ${Object.keys(components).length} components`);
|
|
2065
1753
|
return components;
|
|
2066
1754
|
}
|
|
2067
1755
|
/**
|
|
@@ -2083,7 +1771,7 @@ var ComponentLoader = class {
|
|
|
2083
1771
|
if (!layout || typeof layout !== "string") return /* @__PURE__ */ new Set();
|
|
2084
1772
|
if (!layoutName || typeof layoutName !== "string") return /* @__PURE__ */ new Set();
|
|
2085
1773
|
const cacheKey = `layout_components_${layoutName}`;
|
|
2086
|
-
const cachedComponents = this.router?.cacheManager?.
|
|
1774
|
+
const cachedComponents = this.router?.cacheManager?.get(cacheKey);
|
|
2087
1775
|
if (cachedComponents) {
|
|
2088
1776
|
this.log("debug", `Using cached layout components for '${layoutName}'`);
|
|
2089
1777
|
return cachedComponents;
|
|
@@ -2091,7 +1779,7 @@ var ComponentLoader = class {
|
|
|
2091
1779
|
const componentSet = /* @__PURE__ */ new Set();
|
|
2092
1780
|
this._extractComponentsFromContent(layout, componentSet);
|
|
2093
1781
|
if (this.router?.cacheManager) {
|
|
2094
|
-
this.router.cacheManager.
|
|
1782
|
+
this.router.cacheManager.set(cacheKey, componentSet);
|
|
2095
1783
|
this.log("debug", `Cached layout components for '${layoutName}': ${Array.from(componentSet).join(", ")}`);
|
|
2096
1784
|
}
|
|
2097
1785
|
return componentSet;
|
|
@@ -2298,7 +1986,7 @@ ${template}`;
|
|
|
2298
1986
|
*/
|
|
2299
1987
|
async createVueComponent(routeName) {
|
|
2300
1988
|
const cacheKey = `component_${routeName}`;
|
|
2301
|
-
const cached = this.router.cacheManager?.
|
|
1989
|
+
const cached = this.router.cacheManager?.get(cacheKey);
|
|
2302
1990
|
if (cached) {
|
|
2303
1991
|
return cached;
|
|
2304
1992
|
}
|
|
@@ -2333,7 +2021,7 @@ ${template}`;
|
|
|
2333
2021
|
if (!isProduction) {
|
|
2334
2022
|
const layoutName = script.layout || this.config.defaultLayout;
|
|
2335
2023
|
componentNames = this.componentLoader.getComponentNames(template, layout, layoutName);
|
|
2336
|
-
this.log("
|
|
2024
|
+
this.log("debug", `[DEVELOPMENT] Discovered components for route '${routeName}':`, componentNames);
|
|
2337
2025
|
}
|
|
2338
2026
|
loadedComponents = await this.componentLoader.loadAllComponents(componentNames);
|
|
2339
2027
|
this.log("debug", `Components loaded successfully for route: ${routeName}`);
|
|
@@ -2353,6 +2041,7 @@ ${template}`;
|
|
|
2353
2041
|
...originalData,
|
|
2354
2042
|
currentRoute: routeName,
|
|
2355
2043
|
$query: router.queryManager?.getQueryParams() || {},
|
|
2044
|
+
$params: router.queryManager?.getRouteParams() || {},
|
|
2356
2045
|
$lang: (() => {
|
|
2357
2046
|
try {
|
|
2358
2047
|
return router.i18nManager?.getCurrentLanguage() || router.config.i18nDefaultLanguage || router.config.defaultLanguage || "ko";
|
|
@@ -2374,11 +2063,12 @@ ${template}`;
|
|
|
2374
2063
|
},
|
|
2375
2064
|
async mounted() {
|
|
2376
2065
|
this.$api = router.routeLoader.apiHandler.bindToComponent(this);
|
|
2066
|
+
this.$state = router.stateHandler;
|
|
2377
2067
|
if (script.mounted) {
|
|
2378
2068
|
await script.mounted.call(this);
|
|
2379
2069
|
}
|
|
2380
2070
|
if (script.dataURL) {
|
|
2381
|
-
await this
|
|
2071
|
+
await this.fetchData();
|
|
2382
2072
|
}
|
|
2383
2073
|
await this.$nextTick();
|
|
2384
2074
|
router.routeLoader.formHandler.bindAutoForms(this);
|
|
@@ -2400,18 +2090,22 @@ ${template}`;
|
|
|
2400
2090
|
return key;
|
|
2401
2091
|
}
|
|
2402
2092
|
},
|
|
2403
|
-
// 인증 관련
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2093
|
+
// 인증 관련 (핵심 4개 메소드만)
|
|
2094
|
+
isAuth: () => router.authManager?.isAuthenticated() || false,
|
|
2095
|
+
logout: () => router.authManager ? router.navigateTo(router.authManager.logout()) : null,
|
|
2096
|
+
getToken: () => router.authManager?.getAccessToken() || null,
|
|
2097
|
+
setToken: (token, options) => router.authManager?.setAccessToken(token, options) || false,
|
|
2098
|
+
// i18n 언어 관리
|
|
2099
|
+
getLanguage: () => router.i18nManager?.getCurrentLanguage() || router.config.defaultLanguage || "ko",
|
|
2100
|
+
setLanguage: (lang) => router.i18nManager?.setLanguage(lang),
|
|
2101
|
+
// 로깅 및 에러 처리
|
|
2102
|
+
log: (level, ...args) => {
|
|
2103
|
+
if (router.errorHandler) {
|
|
2104
|
+
router.errorHandler.log(level, `[${routeName}]`, ...args);
|
|
2105
|
+
}
|
|
2106
|
+
},
|
|
2413
2107
|
// 데이터 fetch (ApiHandler 래퍼)
|
|
2414
|
-
async
|
|
2108
|
+
async fetchData(dataConfig = null) {
|
|
2415
2109
|
const configToUse = dataConfig || script.dataURL;
|
|
2416
2110
|
if (!configToUse) return null;
|
|
2417
2111
|
this.$dataLoading = true;
|
|
@@ -2447,7 +2141,7 @@ ${template}`;
|
|
|
2447
2141
|
if (!isProduction && style) {
|
|
2448
2142
|
component._style = style;
|
|
2449
2143
|
}
|
|
2450
|
-
this.router.cacheManager?.
|
|
2144
|
+
this.router.cacheManager?.set(cacheKey, component);
|
|
2451
2145
|
return component;
|
|
2452
2146
|
}
|
|
2453
2147
|
/**
|
|
@@ -2507,7 +2201,7 @@ var ErrorHandler = class {
|
|
|
2507
2201
|
info: 2,
|
|
2508
2202
|
debug: 3
|
|
2509
2203
|
};
|
|
2510
|
-
this.log("
|
|
2204
|
+
this.log("debug", "ErrorHandler", "ErrorHandler initialized with config:", this.config);
|
|
2511
2205
|
}
|
|
2512
2206
|
/**
|
|
2513
2207
|
* 라우트 에러 처리
|
|
@@ -2666,8 +2360,8 @@ var ErrorHandler = class {
|
|
|
2666
2360
|
userAgent: navigator.userAgent,
|
|
2667
2361
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2668
2362
|
routerConfig: {
|
|
2669
|
-
environment: this.router
|
|
2670
|
-
mode: this.router
|
|
2363
|
+
environment: this.router?.config?.environment || "unknown",
|
|
2364
|
+
mode: this.router?.config?.mode || "unknown"
|
|
2671
2365
|
}
|
|
2672
2366
|
};
|
|
2673
2367
|
this.error("ErrorHandler", "\uB77C\uC6B0\uD130 \uC5D0\uB7EC \uB9AC\uD3EC\uD2B8:", errorReport);
|
|
@@ -2762,6 +2456,146 @@ var ErrorHandler = class {
|
|
|
2762
2456
|
}
|
|
2763
2457
|
};
|
|
2764
2458
|
|
|
2459
|
+
// src/core/StateHandler.js
|
|
2460
|
+
var StateHandler = class {
|
|
2461
|
+
constructor(router) {
|
|
2462
|
+
this.router = router;
|
|
2463
|
+
this.state = {};
|
|
2464
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
2465
|
+
this.log("debug", "StateHandler initialized");
|
|
2466
|
+
}
|
|
2467
|
+
/**
|
|
2468
|
+
* 로깅 래퍼 메서드
|
|
2469
|
+
*/
|
|
2470
|
+
log(level, ...args) {
|
|
2471
|
+
if (this.router?.errorHandler) {
|
|
2472
|
+
this.router.errorHandler.log(level, "StateHandler", ...args);
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
/**
|
|
2476
|
+
* 상태 값 설정
|
|
2477
|
+
*/
|
|
2478
|
+
set(key, value) {
|
|
2479
|
+
const oldValue = this.state[key];
|
|
2480
|
+
this.state[key] = value;
|
|
2481
|
+
this.emitChange(key, value, oldValue);
|
|
2482
|
+
this.log("debug", `State set: ${key}`, value);
|
|
2483
|
+
return value;
|
|
2484
|
+
}
|
|
2485
|
+
/**
|
|
2486
|
+
* 상태 값 가져오기
|
|
2487
|
+
*/
|
|
2488
|
+
get(key, defaultValue = void 0) {
|
|
2489
|
+
const value = this.state.hasOwnProperty(key) ? this.state[key] : defaultValue;
|
|
2490
|
+
this.log("debug", `State get: ${key}`, value);
|
|
2491
|
+
return value;
|
|
2492
|
+
}
|
|
2493
|
+
/**
|
|
2494
|
+
* 상태 존재 확인
|
|
2495
|
+
*/
|
|
2496
|
+
has(key) {
|
|
2497
|
+
return this.state.hasOwnProperty(key);
|
|
2498
|
+
}
|
|
2499
|
+
/**
|
|
2500
|
+
* 상태 삭제
|
|
2501
|
+
*/
|
|
2502
|
+
delete(key) {
|
|
2503
|
+
if (this.has(key)) {
|
|
2504
|
+
const oldValue = this.state[key];
|
|
2505
|
+
delete this.state[key];
|
|
2506
|
+
this.emitChange(key, void 0, oldValue);
|
|
2507
|
+
this.log("debug", `State deleted: ${key}`);
|
|
2508
|
+
return true;
|
|
2509
|
+
}
|
|
2510
|
+
return false;
|
|
2511
|
+
}
|
|
2512
|
+
/**
|
|
2513
|
+
* 모든 상태 초기화
|
|
2514
|
+
*/
|
|
2515
|
+
clear() {
|
|
2516
|
+
const keys = Object.keys(this.state);
|
|
2517
|
+
this.state = {};
|
|
2518
|
+
keys.forEach((key) => {
|
|
2519
|
+
this.emitChange(key, void 0, this.state[key]);
|
|
2520
|
+
});
|
|
2521
|
+
this.log("debug", "All state cleared");
|
|
2522
|
+
return keys.length;
|
|
2523
|
+
}
|
|
2524
|
+
/**
|
|
2525
|
+
* 여러 상태 한 번에 설정
|
|
2526
|
+
*/
|
|
2527
|
+
update(updates) {
|
|
2528
|
+
if (!updates || typeof updates !== "object") {
|
|
2529
|
+
this.log("warn", "Invalid updates object provided");
|
|
2530
|
+
return;
|
|
2531
|
+
}
|
|
2532
|
+
Object.entries(updates).forEach(([key, value]) => {
|
|
2533
|
+
this.set(key, value);
|
|
2534
|
+
});
|
|
2535
|
+
}
|
|
2536
|
+
/**
|
|
2537
|
+
* 모든 상태 반환
|
|
2538
|
+
*/
|
|
2539
|
+
getAll() {
|
|
2540
|
+
return { ...this.state };
|
|
2541
|
+
}
|
|
2542
|
+
/**
|
|
2543
|
+
* 상태 변경 리스너 등록
|
|
2544
|
+
*/
|
|
2545
|
+
watch(key, callback) {
|
|
2546
|
+
if (!this.listeners.has(key)) {
|
|
2547
|
+
this.listeners.set(key, []);
|
|
2548
|
+
}
|
|
2549
|
+
this.listeners.get(key).push(callback);
|
|
2550
|
+
this.log("debug", `Watcher added for: ${key}`);
|
|
2551
|
+
}
|
|
2552
|
+
/**
|
|
2553
|
+
* 상태 변경 리스너 제거
|
|
2554
|
+
*/
|
|
2555
|
+
unwatch(key, callback) {
|
|
2556
|
+
if (this.listeners.has(key)) {
|
|
2557
|
+
const callbacks = this.listeners.get(key);
|
|
2558
|
+
const index = callbacks.indexOf(callback);
|
|
2559
|
+
if (index > -1) {
|
|
2560
|
+
callbacks.splice(index, 1);
|
|
2561
|
+
this.log("debug", `Watcher removed for: ${key}`);
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
/**
|
|
2566
|
+
* 상태 변경 이벤트 발생
|
|
2567
|
+
*/
|
|
2568
|
+
emitChange(key, newValue, oldValue) {
|
|
2569
|
+
if (this.listeners.has(key)) {
|
|
2570
|
+
this.listeners.get(key).forEach((callback) => {
|
|
2571
|
+
try {
|
|
2572
|
+
callback(newValue, oldValue, key);
|
|
2573
|
+
} catch (error) {
|
|
2574
|
+
this.log("error", "State watcher error:", error);
|
|
2575
|
+
}
|
|
2576
|
+
});
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
/**
|
|
2580
|
+
* 상태 통계
|
|
2581
|
+
*/
|
|
2582
|
+
getStats() {
|
|
2583
|
+
return {
|
|
2584
|
+
stateCount: Object.keys(this.state).length,
|
|
2585
|
+
watcherCount: Array.from(this.listeners.values()).reduce((sum, arr) => sum + arr.length, 0),
|
|
2586
|
+
keys: Object.keys(this.state)
|
|
2587
|
+
};
|
|
2588
|
+
}
|
|
2589
|
+
/**
|
|
2590
|
+
* 정리 (메모리 누수 방지)
|
|
2591
|
+
*/
|
|
2592
|
+
destroy() {
|
|
2593
|
+
this.state = {};
|
|
2594
|
+
this.listeners.clear();
|
|
2595
|
+
this.log("debug", "StateHandler destroyed");
|
|
2596
|
+
}
|
|
2597
|
+
};
|
|
2598
|
+
|
|
2765
2599
|
// src/viewlogic-router.js
|
|
2766
2600
|
var ViewLogicRouter = class {
|
|
2767
2601
|
constructor(options = {}) {
|
|
@@ -2809,16 +2643,8 @@ var ViewLogicRouter = class {
|
|
|
2809
2643
|
checkAuthFunction: null,
|
|
2810
2644
|
redirectAfterLogin: "home",
|
|
2811
2645
|
authCookieName: "authToken",
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
authCookieOptions: {},
|
|
2815
|
-
authSkipValidation: false,
|
|
2816
|
-
enableParameterValidation: true,
|
|
2817
|
-
maxParameterLength: 1e3,
|
|
2818
|
-
maxParameterCount: 50,
|
|
2819
|
-
maxArraySize: 100,
|
|
2820
|
-
allowedKeyPattern: /^[a-zA-Z0-9_-]+$/,
|
|
2821
|
-
logSecurityWarnings: true
|
|
2646
|
+
authStorage: "localStorage",
|
|
2647
|
+
authSkipValidation: false
|
|
2822
2648
|
};
|
|
2823
2649
|
const config = { ...defaults, ...options };
|
|
2824
2650
|
config.srcPath = this.resolvePath(config.srcPath, config.basePath);
|
|
@@ -2885,8 +2711,9 @@ var ViewLogicRouter = class {
|
|
|
2885
2711
|
async initialize() {
|
|
2886
2712
|
try {
|
|
2887
2713
|
this.cacheManager = new CacheManager(this, this.config);
|
|
2714
|
+
this.stateHandler = new StateHandler(this);
|
|
2888
2715
|
this.routeLoader = new RouteLoader(this, this.config);
|
|
2889
|
-
this.queryManager = new QueryManager(this
|
|
2716
|
+
this.queryManager = new QueryManager(this);
|
|
2890
2717
|
this.errorHandler = new ErrorHandler(this, this.config);
|
|
2891
2718
|
if (this.config.useI18n) {
|
|
2892
2719
|
try {
|