viewlogic 1.2.2 → 1.2.3
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 +607 -863
- package/dist/viewlogic-router.js +280 -453
- 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();
|
|
@@ -498,7 +398,7 @@ var AuthManager = class {
|
|
|
498
398
|
return { allowed: false, reason: "custom_auth_error", error };
|
|
499
399
|
}
|
|
500
400
|
}
|
|
501
|
-
const isAuthenticated = this.
|
|
401
|
+
const isAuthenticated = this.isAuthenticated();
|
|
502
402
|
return {
|
|
503
403
|
allowed: isAuthenticated,
|
|
504
404
|
reason: isAuthenticated ? "authenticated" : "not_authenticated",
|
|
@@ -506,56 +406,40 @@ var AuthManager = class {
|
|
|
506
406
|
};
|
|
507
407
|
}
|
|
508
408
|
/**
|
|
509
|
-
*
|
|
409
|
+
* JWT 토큰 검증
|
|
510
410
|
*/
|
|
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
|
-
}
|
|
411
|
+
isTokenValid(token) {
|
|
412
|
+
if (!token) return false;
|
|
413
|
+
try {
|
|
414
|
+
if (token.includes(".")) {
|
|
415
|
+
const payload = JSON.parse(atob(token.split(".")[1]));
|
|
416
|
+
if (payload.exp && Date.now() >= payload.exp * 1e3) {
|
|
417
|
+
return false;
|
|
524
418
|
}
|
|
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
419
|
}
|
|
530
|
-
}
|
|
531
|
-
const sessionToken = sessionStorage.getItem("authToken") || sessionStorage.getItem("accessToken");
|
|
532
|
-
if (sessionToken) {
|
|
533
|
-
this.log("debug", "\u2705 Token found in sessionStorage");
|
|
534
420
|
return true;
|
|
421
|
+
} catch (error) {
|
|
422
|
+
this.log("warn", "Token validation failed:", error);
|
|
423
|
+
return false;
|
|
535
424
|
}
|
|
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
|
-
}
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* 사용자 인증 상태 확인
|
|
428
|
+
*/
|
|
429
|
+
isAuthenticated() {
|
|
430
|
+
this.log("debug", "\u{1F50D} Checking user authentication status");
|
|
431
|
+
const token = this.getAccessToken();
|
|
432
|
+
if (!token) {
|
|
433
|
+
this.log("debug", "\u274C No token found");
|
|
434
|
+
return false;
|
|
552
435
|
}
|
|
553
|
-
if (
|
|
554
|
-
this.log("debug", "
|
|
555
|
-
|
|
436
|
+
if (!this.isTokenValid(token)) {
|
|
437
|
+
this.log("debug", "Token expired, removing...");
|
|
438
|
+
this.removeAccessToken();
|
|
439
|
+
return false;
|
|
556
440
|
}
|
|
557
|
-
this.log("debug", "\
|
|
558
|
-
return
|
|
441
|
+
this.log("debug", "\u2705 Valid token found");
|
|
442
|
+
return true;
|
|
559
443
|
}
|
|
560
444
|
/**
|
|
561
445
|
* 공개 라우트인지 확인
|
|
@@ -581,18 +465,7 @@ var AuthManager = class {
|
|
|
581
465
|
* 인증 쿠키 가져오기
|
|
582
466
|
*/
|
|
583
467
|
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;
|
|
468
|
+
return this.getCookieValue(this.config.authCookieName);
|
|
596
469
|
}
|
|
597
470
|
/**
|
|
598
471
|
* 쿠키 값 가져오기
|
|
@@ -609,24 +482,18 @@ var AuthManager = class {
|
|
|
609
482
|
* 인증 쿠키 제거
|
|
610
483
|
*/
|
|
611
484
|
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");
|
|
485
|
+
document.cookie = `${this.config.authCookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
|
|
486
|
+
this.log("debug", "Auth cookie removed");
|
|
618
487
|
}
|
|
619
488
|
/**
|
|
620
489
|
* 액세스 토큰 가져오기
|
|
621
490
|
*/
|
|
622
491
|
getAccessToken() {
|
|
623
|
-
let token = localStorage.getItem("authToken")
|
|
492
|
+
let token = localStorage.getItem("authToken");
|
|
624
493
|
if (token) return token;
|
|
625
|
-
token = sessionStorage.getItem("authToken")
|
|
494
|
+
token = sessionStorage.getItem("authToken");
|
|
626
495
|
if (token) return token;
|
|
627
|
-
|
|
628
|
-
if (token) return token;
|
|
629
|
-
return null;
|
|
496
|
+
return this.getAuthCookie();
|
|
630
497
|
}
|
|
631
498
|
/**
|
|
632
499
|
* 액세스 토큰 설정
|
|
@@ -642,17 +509,9 @@ var AuthManager = class {
|
|
|
642
509
|
skipValidation = this.config.authSkipValidation
|
|
643
510
|
} = options;
|
|
644
511
|
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
|
-
}
|
|
512
|
+
if (!skipValidation && !this.isTokenValid(token)) {
|
|
513
|
+
this.log("warn", "\u274C Token is expired or invalid");
|
|
514
|
+
return false;
|
|
656
515
|
}
|
|
657
516
|
switch (storage) {
|
|
658
517
|
case "localStorage":
|
|
@@ -664,7 +523,7 @@ var AuthManager = class {
|
|
|
664
523
|
this.log("debug", "Token saved to sessionStorage");
|
|
665
524
|
break;
|
|
666
525
|
case "cookie":
|
|
667
|
-
this.setAuthCookie(token
|
|
526
|
+
this.setAuthCookie(token);
|
|
668
527
|
break;
|
|
669
528
|
default:
|
|
670
529
|
localStorage.setItem("authToken", token);
|
|
@@ -684,41 +543,25 @@ var AuthManager = class {
|
|
|
684
543
|
/**
|
|
685
544
|
* 인증 쿠키 설정
|
|
686
545
|
*/
|
|
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}`;
|
|
546
|
+
setAuthCookie(token) {
|
|
547
|
+
const secure = window.location.protocol === "https:";
|
|
548
|
+
let cookieString = `${this.config.authCookieName}=${encodeURIComponent(token)}; path=/; SameSite=Strict`;
|
|
696
549
|
if (secure) {
|
|
697
550
|
cookieString += "; Secure";
|
|
698
551
|
}
|
|
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");
|
|
552
|
+
if (token.includes(".")) {
|
|
553
|
+
try {
|
|
554
|
+
const payload = JSON.parse(atob(token.split(".")[1]));
|
|
555
|
+
if (payload.exp) {
|
|
556
|
+
const expireDate = new Date(payload.exp * 1e3);
|
|
557
|
+
cookieString += `; Expires=${expireDate.toUTCString()}`;
|
|
715
558
|
}
|
|
559
|
+
} catch (error) {
|
|
560
|
+
this.log("warn", "Could not extract expiration from JWT token");
|
|
716
561
|
}
|
|
717
|
-
} catch (error) {
|
|
718
|
-
this.log("Token processing error:", error);
|
|
719
562
|
}
|
|
720
563
|
document.cookie = cookieString;
|
|
721
|
-
this.log(
|
|
564
|
+
this.log("debug", "Auth cookie set");
|
|
722
565
|
}
|
|
723
566
|
/**
|
|
724
567
|
* 토큰 제거
|
|
@@ -739,9 +582,7 @@ var AuthManager = class {
|
|
|
739
582
|
case "all":
|
|
740
583
|
default:
|
|
741
584
|
localStorage.removeItem("authToken");
|
|
742
|
-
localStorage.removeItem("accessToken");
|
|
743
585
|
sessionStorage.removeItem("authToken");
|
|
744
|
-
sessionStorage.removeItem("accessToken");
|
|
745
586
|
this.removeAuthCookie();
|
|
746
587
|
break;
|
|
747
588
|
}
|
|
@@ -751,7 +592,7 @@ var AuthManager = class {
|
|
|
751
592
|
/**
|
|
752
593
|
* 로그인 성공 처리
|
|
753
594
|
*/
|
|
754
|
-
|
|
595
|
+
loginSuccess(targetRoute = null) {
|
|
755
596
|
const redirectRoute = targetRoute || this.config.redirectAfterLogin;
|
|
756
597
|
this.log(`\u{1F389} Login success, redirecting to: ${redirectRoute}`);
|
|
757
598
|
this.emitAuthEvent("login_success", { targetRoute: redirectRoute });
|
|
@@ -763,11 +604,9 @@ var AuthManager = class {
|
|
|
763
604
|
/**
|
|
764
605
|
* 로그아웃 처리
|
|
765
606
|
*/
|
|
766
|
-
|
|
607
|
+
logout() {
|
|
767
608
|
this.log("\u{1F44B} Logging out user");
|
|
768
609
|
this.removeAccessToken();
|
|
769
|
-
if (window.user) window.user = null;
|
|
770
|
-
if (window.isAuthenticated) window.isAuthenticated = false;
|
|
771
610
|
this.emitAuthEvent("logout", {});
|
|
772
611
|
if (this.router && typeof this.router.navigateTo === "function") {
|
|
773
612
|
this.router.navigateTo(this.config.loginRoute);
|
|
@@ -824,7 +663,7 @@ var AuthManager = class {
|
|
|
824
663
|
getAuthStats() {
|
|
825
664
|
return {
|
|
826
665
|
enabled: this.config.enabled,
|
|
827
|
-
isAuthenticated: this.
|
|
666
|
+
isAuthenticated: this.isAuthenticated(),
|
|
828
667
|
hasToken: !!this.getAccessToken(),
|
|
829
668
|
protectedRoutesCount: this.config.protectedRoutes.length,
|
|
830
669
|
protectedPrefixesCount: this.config.protectedPrefixes.length,
|
|
@@ -850,9 +689,8 @@ var CacheManager = class {
|
|
|
850
689
|
// 'memory' 또는 'lru'
|
|
851
690
|
cacheTTL: options.cacheTTL || 3e5,
|
|
852
691
|
// 5분 (밀리초)
|
|
853
|
-
maxCacheSize: options.maxCacheSize || 50
|
|
692
|
+
maxCacheSize: options.maxCacheSize || 50
|
|
854
693
|
// LRU 캐시 최대 크기
|
|
855
|
-
debug: options.debug || false
|
|
856
694
|
};
|
|
857
695
|
this.router = router;
|
|
858
696
|
this.cache = /* @__PURE__ */ new Map();
|
|
@@ -871,7 +709,7 @@ var CacheManager = class {
|
|
|
871
709
|
/**
|
|
872
710
|
* 캐시에 값 저장
|
|
873
711
|
*/
|
|
874
|
-
|
|
712
|
+
set(key, value) {
|
|
875
713
|
const now = Date.now();
|
|
876
714
|
if (this.config.cacheMode === "lru") {
|
|
877
715
|
if (this.cache.size >= this.config.maxCacheSize && !this.cache.has(key)) {
|
|
@@ -895,7 +733,7 @@ var CacheManager = class {
|
|
|
895
733
|
/**
|
|
896
734
|
* 캐시에서 값 가져오기
|
|
897
735
|
*/
|
|
898
|
-
|
|
736
|
+
get(key) {
|
|
899
737
|
const now = Date.now();
|
|
900
738
|
const timestamp = this.cacheTimestamps.get(key);
|
|
901
739
|
if (timestamp && now - timestamp > this.config.cacheTTL) {
|
|
@@ -928,13 +766,13 @@ var CacheManager = class {
|
|
|
928
766
|
/**
|
|
929
767
|
* 캐시에 키가 있는지 확인
|
|
930
768
|
*/
|
|
931
|
-
|
|
932
|
-
return this.cache.has(key) && this.
|
|
769
|
+
has(key) {
|
|
770
|
+
return this.cache.has(key) && this.get(key) !== null;
|
|
933
771
|
}
|
|
934
772
|
/**
|
|
935
773
|
* 특정 키 패턴의 캐시 삭제
|
|
936
774
|
*/
|
|
937
|
-
|
|
775
|
+
deleteByPattern(pattern) {
|
|
938
776
|
const keysToDelete = [];
|
|
939
777
|
for (const key of this.cache.keys()) {
|
|
940
778
|
if (key.includes(pattern) || key.startsWith(pattern)) {
|
|
@@ -951,13 +789,13 @@ var CacheManager = class {
|
|
|
951
789
|
}
|
|
952
790
|
}
|
|
953
791
|
});
|
|
954
|
-
this.log("debug", `\u{1F9F9}
|
|
792
|
+
this.log("debug", `\u{1F9F9} Deleted ${keysToDelete.length} cache entries matching: ${pattern}`);
|
|
955
793
|
return keysToDelete.length;
|
|
956
794
|
}
|
|
957
795
|
/**
|
|
958
|
-
* 특정 컴포넌트 캐시
|
|
796
|
+
* 특정 컴포넌트 캐시 삭제
|
|
959
797
|
*/
|
|
960
|
-
|
|
798
|
+
deleteComponent(routeName) {
|
|
961
799
|
const patterns = [
|
|
962
800
|
`component_${routeName}`,
|
|
963
801
|
`script_${routeName}`,
|
|
@@ -967,27 +805,27 @@ var CacheManager = class {
|
|
|
967
805
|
];
|
|
968
806
|
let totalInvalidated = 0;
|
|
969
807
|
patterns.forEach((pattern) => {
|
|
970
|
-
totalInvalidated += this.
|
|
808
|
+
totalInvalidated += this.deleteByPattern(pattern);
|
|
971
809
|
});
|
|
972
|
-
this.log(`\u{1F504}
|
|
810
|
+
this.log(`\u{1F504} Deleted component cache for route: ${routeName} (${totalInvalidated} entries)`);
|
|
973
811
|
return totalInvalidated;
|
|
974
812
|
}
|
|
975
813
|
/**
|
|
976
814
|
* 모든 컴포넌트 캐시 삭제
|
|
977
815
|
*/
|
|
978
|
-
|
|
816
|
+
deleteAllComponents() {
|
|
979
817
|
const componentPatterns = ["component_", "script_", "template_", "style_", "layout_"];
|
|
980
818
|
let totalCleared = 0;
|
|
981
819
|
componentPatterns.forEach((pattern) => {
|
|
982
|
-
totalCleared += this.
|
|
820
|
+
totalCleared += this.deleteByPattern(pattern);
|
|
983
821
|
});
|
|
984
|
-
this.log(`\u{1F9FD}
|
|
822
|
+
this.log(`\u{1F9FD} Deleted all component caches (${totalCleared} entries)`);
|
|
985
823
|
return totalCleared;
|
|
986
824
|
}
|
|
987
825
|
/**
|
|
988
826
|
* 전체 캐시 삭제
|
|
989
827
|
*/
|
|
990
|
-
|
|
828
|
+
clearAll() {
|
|
991
829
|
const size = this.cache.size;
|
|
992
830
|
this.cache.clear();
|
|
993
831
|
this.cacheTimestamps.clear();
|
|
@@ -998,7 +836,7 @@ var CacheManager = class {
|
|
|
998
836
|
/**
|
|
999
837
|
* 만료된 캐시 항목들 정리
|
|
1000
838
|
*/
|
|
1001
|
-
|
|
839
|
+
cleanExpired() {
|
|
1002
840
|
const now = Date.now();
|
|
1003
841
|
const expiredKeys = [];
|
|
1004
842
|
for (const [key, timestamp] of this.cacheTimestamps.entries()) {
|
|
@@ -1024,15 +862,15 @@ var CacheManager = class {
|
|
|
1024
862
|
/**
|
|
1025
863
|
* 캐시 통계 정보
|
|
1026
864
|
*/
|
|
1027
|
-
|
|
865
|
+
getStats() {
|
|
1028
866
|
return {
|
|
1029
867
|
size: this.cache.size,
|
|
1030
868
|
maxSize: this.config.maxCacheSize,
|
|
1031
869
|
mode: this.config.cacheMode,
|
|
1032
870
|
ttl: this.config.cacheTTL,
|
|
1033
871
|
memoryUsage: this.getMemoryUsage(),
|
|
1034
|
-
hitRatio: this.
|
|
1035
|
-
categories: this.
|
|
872
|
+
hitRatio: this.getHitRate(),
|
|
873
|
+
categories: this.getStatsByCategory()
|
|
1036
874
|
};
|
|
1037
875
|
}
|
|
1038
876
|
/**
|
|
@@ -1059,14 +897,14 @@ var CacheManager = class {
|
|
|
1059
897
|
/**
|
|
1060
898
|
* 히트 비율 계산 (간단한 추정)
|
|
1061
899
|
*/
|
|
1062
|
-
|
|
900
|
+
getHitRate() {
|
|
1063
901
|
const ratio = this.cache.size > 0 ? Math.min(this.cache.size / this.config.maxCacheSize, 1) : 0;
|
|
1064
902
|
return Math.round(ratio * 100);
|
|
1065
903
|
}
|
|
1066
904
|
/**
|
|
1067
905
|
* 카테고리별 캐시 통계
|
|
1068
906
|
*/
|
|
1069
|
-
|
|
907
|
+
getStatsByCategory() {
|
|
1070
908
|
const categories = {
|
|
1071
909
|
components: 0,
|
|
1072
910
|
scripts: 0,
|
|
@@ -1088,14 +926,14 @@ var CacheManager = class {
|
|
|
1088
926
|
/**
|
|
1089
927
|
* 캐시 키 목록 반환
|
|
1090
928
|
*/
|
|
1091
|
-
|
|
929
|
+
getKeys() {
|
|
1092
930
|
return Array.from(this.cache.keys());
|
|
1093
931
|
}
|
|
1094
932
|
/**
|
|
1095
933
|
* 특정 패턴의 캐시 키들 반환
|
|
1096
934
|
*/
|
|
1097
|
-
|
|
1098
|
-
return this.
|
|
935
|
+
getKeysByPattern(pattern) {
|
|
936
|
+
return this.getKeys().filter(
|
|
1099
937
|
(key) => key.includes(pattern) || key.startsWith(pattern)
|
|
1100
938
|
);
|
|
1101
939
|
}
|
|
@@ -1107,7 +945,7 @@ var CacheManager = class {
|
|
|
1107
945
|
clearInterval(this.cleanupInterval);
|
|
1108
946
|
}
|
|
1109
947
|
this.cleanupInterval = setInterval(() => {
|
|
1110
|
-
this.
|
|
948
|
+
this.cleanExpired();
|
|
1111
949
|
}, interval);
|
|
1112
950
|
this.log(`\u{1F916} Auto cleanup started (interval: ${interval}ms)`);
|
|
1113
951
|
}
|
|
@@ -1126,27 +964,18 @@ var CacheManager = class {
|
|
|
1126
964
|
*/
|
|
1127
965
|
destroy() {
|
|
1128
966
|
this.stopAutoCleanup();
|
|
1129
|
-
this.
|
|
967
|
+
this.clearAll();
|
|
1130
968
|
this.log("debug", "CacheManager destroyed");
|
|
1131
969
|
}
|
|
1132
970
|
};
|
|
1133
971
|
|
|
1134
972
|
// src/plugins/QueryManager.js
|
|
1135
973
|
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
|
-
};
|
|
974
|
+
constructor(router) {
|
|
1146
975
|
this.router = router;
|
|
1147
976
|
this.currentQueryParams = {};
|
|
1148
977
|
this.currentRouteParams = {};
|
|
1149
|
-
this.log("
|
|
978
|
+
this.log("debug", "QueryManager initialized");
|
|
1150
979
|
}
|
|
1151
980
|
/**
|
|
1152
981
|
* 로깅 래퍼 메서드
|
|
@@ -1156,97 +985,6 @@ var QueryManager = class {
|
|
|
1156
985
|
this.router.errorHandler.log(level, "QueryManager", ...args);
|
|
1157
986
|
}
|
|
1158
987
|
}
|
|
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
988
|
/**
|
|
1251
989
|
* 쿼리스트링 파싱
|
|
1252
990
|
*/
|
|
@@ -1255,56 +993,22 @@ var QueryManager = class {
|
|
|
1255
993
|
if (!queryString) return params;
|
|
1256
994
|
const pairs = queryString.split("&");
|
|
1257
995
|
for (const pair of pairs) {
|
|
996
|
+
const [rawKey, rawValue] = pair.split("=");
|
|
997
|
+
if (!rawKey) continue;
|
|
1258
998
|
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);
|
|
999
|
+
const key = decodeURIComponent(rawKey);
|
|
1000
|
+
const value = rawValue ? decodeURIComponent(rawValue) : "";
|
|
1274
1001
|
if (key.endsWith("[]")) {
|
|
1275
1002
|
const arrayKey = key.slice(0, -2);
|
|
1276
|
-
if (!this.validateParameter(arrayKey, [])) {
|
|
1277
|
-
continue;
|
|
1278
|
-
}
|
|
1279
1003
|
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
|
-
}
|
|
1004
|
+
params[arrayKey].push(value);
|
|
1287
1005
|
} else {
|
|
1288
|
-
params[key] =
|
|
1006
|
+
params[key] = value;
|
|
1289
1007
|
}
|
|
1290
1008
|
} catch (error) {
|
|
1291
|
-
this.log("
|
|
1009
|
+
this.log("warn", "Failed to decode query parameter:", pair);
|
|
1292
1010
|
}
|
|
1293
1011
|
}
|
|
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++;
|
|
1305
|
-
}
|
|
1306
|
-
return limitedParams;
|
|
1307
|
-
}
|
|
1308
1012
|
return params;
|
|
1309
1013
|
}
|
|
1310
1014
|
/**
|
|
@@ -1358,36 +1062,17 @@ var QueryManager = class {
|
|
|
1358
1062
|
*/
|
|
1359
1063
|
setQueryParams(params, replace = false) {
|
|
1360
1064
|
if (!params || typeof params !== "object") {
|
|
1361
|
-
|
|
1065
|
+
this.log("warn", "Invalid parameters object provided to setQueryParams");
|
|
1362
1066
|
return;
|
|
1363
1067
|
}
|
|
1364
1068
|
const currentParams = replace ? {} : { ...this.currentQueryParams };
|
|
1365
|
-
const sanitizedParams = {};
|
|
1366
1069
|
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 === "") {
|
|
1070
|
+
if (value !== void 0 && value !== null && value !== "") {
|
|
1071
|
+
currentParams[key] = value;
|
|
1072
|
+
} else {
|
|
1382
1073
|
delete currentParams[key];
|
|
1383
1074
|
}
|
|
1384
1075
|
}
|
|
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
1076
|
this.currentQueryParams = currentParams;
|
|
1392
1077
|
this.updateURL();
|
|
1393
1078
|
}
|
|
@@ -1466,8 +1151,6 @@ var QueryManager = class {
|
|
|
1466
1151
|
getStats() {
|
|
1467
1152
|
return {
|
|
1468
1153
|
currentParams: Object.keys(this.currentQueryParams).length,
|
|
1469
|
-
maxAllowed: this.config.maxParameterCount,
|
|
1470
|
-
validationEnabled: this.config.enableParameterValidation,
|
|
1471
1154
|
currentQueryString: this.buildQueryString(this.currentQueryParams)
|
|
1472
1155
|
};
|
|
1473
1156
|
}
|
|
@@ -1938,7 +1621,7 @@ var ComponentLoader = class {
|
|
|
1938
1621
|
throw new Error("Component name must be a non-empty string");
|
|
1939
1622
|
}
|
|
1940
1623
|
const cacheKey = `component_${componentName}`;
|
|
1941
|
-
const cachedComponent = this.router?.cacheManager?.
|
|
1624
|
+
const cachedComponent = this.router?.cacheManager?.get(cacheKey);
|
|
1942
1625
|
if (cachedComponent) {
|
|
1943
1626
|
this.log("debug", `Component '${componentName}' loaded from cache`);
|
|
1944
1627
|
return cachedComponent;
|
|
@@ -1951,7 +1634,7 @@ var ComponentLoader = class {
|
|
|
1951
1634
|
try {
|
|
1952
1635
|
const component = await loadPromise;
|
|
1953
1636
|
if (component && this.router?.cacheManager) {
|
|
1954
|
-
this.router.cacheManager.
|
|
1637
|
+
this.router.cacheManager.set(cacheKey, component);
|
|
1955
1638
|
this.log("debug", `Component '${componentName}' cached successfully`);
|
|
1956
1639
|
}
|
|
1957
1640
|
return component;
|
|
@@ -2025,11 +1708,11 @@ var ComponentLoader = class {
|
|
|
2025
1708
|
async _loadProductionComponents() {
|
|
2026
1709
|
try {
|
|
2027
1710
|
const componentsPath = `${this.router?.config?.routesPath || "/routes"}/_components.js`;
|
|
2028
|
-
this.log("
|
|
1711
|
+
this.log("debug", "[PRODUCTION] Loading unified components from:", componentsPath);
|
|
2029
1712
|
const componentsModule = await import(componentsPath);
|
|
2030
1713
|
if (typeof componentsModule.registerComponents === "function") {
|
|
2031
1714
|
this.unifiedComponents = componentsModule.components || {};
|
|
2032
|
-
this.log("
|
|
1715
|
+
this.log("debug", `[PRODUCTION] Unified components loaded: ${Object.keys(this.unifiedComponents).length} components`);
|
|
2033
1716
|
return this.unifiedComponents;
|
|
2034
1717
|
} else {
|
|
2035
1718
|
throw new Error("registerComponents function not found in components module");
|
|
@@ -2047,10 +1730,10 @@ var ComponentLoader = class {
|
|
|
2047
1730
|
const namesToLoad = componentNames || [];
|
|
2048
1731
|
const components = {};
|
|
2049
1732
|
if (namesToLoad.length === 0) {
|
|
2050
|
-
this.log("
|
|
1733
|
+
this.log("debug", "[DEVELOPMENT] No components to load");
|
|
2051
1734
|
return components;
|
|
2052
1735
|
}
|
|
2053
|
-
this.log("
|
|
1736
|
+
this.log("debug", `[DEVELOPMENT] Loading individual components: ${namesToLoad.join(", ")}`);
|
|
2054
1737
|
for (const name of namesToLoad) {
|
|
2055
1738
|
try {
|
|
2056
1739
|
const component = await this.loadComponent(name);
|
|
@@ -2061,7 +1744,7 @@ var ComponentLoader = class {
|
|
|
2061
1744
|
this.log("warn", `[DEVELOPMENT] Failed to load component ${name}:`, loadError.message);
|
|
2062
1745
|
}
|
|
2063
1746
|
}
|
|
2064
|
-
this.log("
|
|
1747
|
+
this.log("debug", `[DEVELOPMENT] Individual components loaded: ${Object.keys(components).length} components`);
|
|
2065
1748
|
return components;
|
|
2066
1749
|
}
|
|
2067
1750
|
/**
|
|
@@ -2083,7 +1766,7 @@ var ComponentLoader = class {
|
|
|
2083
1766
|
if (!layout || typeof layout !== "string") return /* @__PURE__ */ new Set();
|
|
2084
1767
|
if (!layoutName || typeof layoutName !== "string") return /* @__PURE__ */ new Set();
|
|
2085
1768
|
const cacheKey = `layout_components_${layoutName}`;
|
|
2086
|
-
const cachedComponents = this.router?.cacheManager?.
|
|
1769
|
+
const cachedComponents = this.router?.cacheManager?.get(cacheKey);
|
|
2087
1770
|
if (cachedComponents) {
|
|
2088
1771
|
this.log("debug", `Using cached layout components for '${layoutName}'`);
|
|
2089
1772
|
return cachedComponents;
|
|
@@ -2091,7 +1774,7 @@ var ComponentLoader = class {
|
|
|
2091
1774
|
const componentSet = /* @__PURE__ */ new Set();
|
|
2092
1775
|
this._extractComponentsFromContent(layout, componentSet);
|
|
2093
1776
|
if (this.router?.cacheManager) {
|
|
2094
|
-
this.router.cacheManager.
|
|
1777
|
+
this.router.cacheManager.set(cacheKey, componentSet);
|
|
2095
1778
|
this.log("debug", `Cached layout components for '${layoutName}': ${Array.from(componentSet).join(", ")}`);
|
|
2096
1779
|
}
|
|
2097
1780
|
return componentSet;
|
|
@@ -2298,7 +1981,7 @@ ${template}`;
|
|
|
2298
1981
|
*/
|
|
2299
1982
|
async createVueComponent(routeName) {
|
|
2300
1983
|
const cacheKey = `component_${routeName}`;
|
|
2301
|
-
const cached = this.router.cacheManager?.
|
|
1984
|
+
const cached = this.router.cacheManager?.get(cacheKey);
|
|
2302
1985
|
if (cached) {
|
|
2303
1986
|
return cached;
|
|
2304
1987
|
}
|
|
@@ -2333,7 +2016,7 @@ ${template}`;
|
|
|
2333
2016
|
if (!isProduction) {
|
|
2334
2017
|
const layoutName = script.layout || this.config.defaultLayout;
|
|
2335
2018
|
componentNames = this.componentLoader.getComponentNames(template, layout, layoutName);
|
|
2336
|
-
this.log("
|
|
2019
|
+
this.log("debug", `[DEVELOPMENT] Discovered components for route '${routeName}':`, componentNames);
|
|
2337
2020
|
}
|
|
2338
2021
|
loadedComponents = await this.componentLoader.loadAllComponents(componentNames);
|
|
2339
2022
|
this.log("debug", `Components loaded successfully for route: ${routeName}`);
|
|
@@ -2401,15 +2084,26 @@ ${template}`;
|
|
|
2401
2084
|
}
|
|
2402
2085
|
},
|
|
2403
2086
|
// 인증 관련
|
|
2404
|
-
$isAuthenticated: () => router.authManager?.
|
|
2405
|
-
$logout: () => router.authManager ? router.navigateTo(router.authManager.
|
|
2406
|
-
$loginSuccess: (target) => router.authManager ? router.navigateTo(router.authManager.
|
|
2087
|
+
$isAuthenticated: () => router.authManager?.isAuthenticated() || false,
|
|
2088
|
+
$logout: () => router.authManager ? router.navigateTo(router.authManager.logout()) : null,
|
|
2089
|
+
$loginSuccess: (target) => router.authManager ? router.navigateTo(router.authManager.loginSuccess(target)) : null,
|
|
2407
2090
|
$checkAuth: (route) => router.authManager ? router.authManager.checkAuthentication(route) : Promise.resolve({ allowed: true, reason: "auth_disabled" }),
|
|
2408
2091
|
$getToken: () => router.authManager?.getAccessToken() || null,
|
|
2409
2092
|
$setToken: (token, options) => router.authManager?.setAccessToken(token, options) || false,
|
|
2410
2093
|
$removeToken: (storage) => router.authManager?.removeAccessToken(storage) || null,
|
|
2411
2094
|
$getAuthCookie: () => router.authManager?.getAuthCookie() || null,
|
|
2412
2095
|
$getCookie: (name) => router.authManager?.getCookieValue(name) || null,
|
|
2096
|
+
// 상태 관리
|
|
2097
|
+
$state: {
|
|
2098
|
+
get: (key, defaultValue) => router.stateHandler?.get(key, defaultValue),
|
|
2099
|
+
set: (key, value) => router.stateHandler?.set(key, value),
|
|
2100
|
+
has: (key) => router.stateHandler?.has(key) || false,
|
|
2101
|
+
delete: (key) => router.stateHandler?.delete(key) || false,
|
|
2102
|
+
update: (updates) => router.stateHandler?.update(updates),
|
|
2103
|
+
watch: (key, callback) => router.stateHandler?.watch(key, callback),
|
|
2104
|
+
unwatch: (key, callback) => router.stateHandler?.unwatch(key, callback),
|
|
2105
|
+
getAll: () => router.stateHandler?.getAll() || {}
|
|
2106
|
+
},
|
|
2413
2107
|
// 데이터 fetch (ApiHandler 래퍼)
|
|
2414
2108
|
async $fetchData(dataConfig = null) {
|
|
2415
2109
|
const configToUse = dataConfig || script.dataURL;
|
|
@@ -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 {
|