@sonhoseong/mfa-lib 1.0.0

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.
Files changed (98) hide show
  1. package/README.md +172 -0
  2. package/dist/components/button/ScrollTopButton.d.ts +5 -0
  3. package/dist/components/button/ScrollTopButton.d.ts.map +1 -0
  4. package/dist/components/button/ScrollTopButton.js +28 -0
  5. package/dist/components/button/index.d.ts +3 -0
  6. package/dist/components/button/index.d.ts.map +1 -0
  7. package/dist/components/button/index.js +2 -0
  8. package/dist/components/button/types/index.d.ts +10 -0
  9. package/dist/components/button/types/index.d.ts.map +1 -0
  10. package/dist/components/button/types/index.js +1 -0
  11. package/dist/components/error/ErrorBoundary.d.ts +25 -0
  12. package/dist/components/error/ErrorBoundary.d.ts.map +1 -0
  13. package/dist/components/error/ErrorBoundary.js +130 -0
  14. package/dist/components/error/index.d.ts +2 -0
  15. package/dist/components/error/index.d.ts.map +1 -0
  16. package/dist/components/error/index.js +1 -0
  17. package/dist/components/index.d.ts +6 -0
  18. package/dist/components/index.d.ts.map +1 -0
  19. package/dist/components/index.js +10 -0
  20. package/dist/components/loading/GlobalLoading.d.ts +12 -0
  21. package/dist/components/loading/GlobalLoading.d.ts.map +1 -0
  22. package/dist/components/loading/GlobalLoading.js +84 -0
  23. package/dist/components/loading/index.d.ts +2 -0
  24. package/dist/components/loading/index.d.ts.map +1 -0
  25. package/dist/components/loading/index.js +1 -0
  26. package/dist/components/modal/ModalContainer.d.ts +8 -0
  27. package/dist/components/modal/ModalContainer.d.ts.map +1 -0
  28. package/dist/components/modal/ModalContainer.js +161 -0
  29. package/dist/components/modal/ModalContext.d.ts +43 -0
  30. package/dist/components/modal/ModalContext.d.ts.map +1 -0
  31. package/dist/components/modal/ModalContext.js +78 -0
  32. package/dist/components/modal/index.d.ts +4 -0
  33. package/dist/components/modal/index.d.ts.map +1 -0
  34. package/dist/components/modal/index.js +2 -0
  35. package/dist/components/toast/ToastContainer.d.ts +12 -0
  36. package/dist/components/toast/ToastContainer.d.ts.map +1 -0
  37. package/dist/components/toast/ToastContainer.js +125 -0
  38. package/dist/components/toast/ToastContext.d.ts +40 -0
  39. package/dist/components/toast/ToastContext.d.ts.map +1 -0
  40. package/dist/components/toast/ToastContext.js +56 -0
  41. package/dist/components/toast/index.d.ts +4 -0
  42. package/dist/components/toast/index.d.ts.map +1 -0
  43. package/dist/components/toast/index.js +2 -0
  44. package/dist/hooks/index.d.ts +11 -0
  45. package/dist/hooks/index.d.ts.map +1 -0
  46. package/dist/hooks/index.js +17 -0
  47. package/dist/hooks/use-auth.d.ts +38 -0
  48. package/dist/hooks/use-auth.d.ts.map +1 -0
  49. package/dist/hooks/use-auth.js +117 -0
  50. package/dist/hooks/use-error-notification.d.ts +29 -0
  51. package/dist/hooks/use-error-notification.d.ts.map +1 -0
  52. package/dist/hooks/use-error-notification.js +91 -0
  53. package/dist/hooks/use-global-loading.d.ts +16 -0
  54. package/dist/hooks/use-global-loading.d.ts.map +1 -0
  55. package/dist/hooks/use-global-loading.js +72 -0
  56. package/dist/hooks/use-initialize.d.ts +26 -0
  57. package/dist/hooks/use-initialize.d.ts.map +1 -0
  58. package/dist/hooks/use-initialize.js +104 -0
  59. package/dist/hooks/use-modal.d.ts +39 -0
  60. package/dist/hooks/use-modal.d.ts.map +1 -0
  61. package/dist/hooks/use-modal.js +83 -0
  62. package/dist/hooks/use-navigate.d.ts +34 -0
  63. package/dist/hooks/use-navigate.d.ts.map +1 -0
  64. package/dist/hooks/use-navigate.js +89 -0
  65. package/dist/hooks/use-track-history.d.ts +33 -0
  66. package/dist/hooks/use-track-history.d.ts.map +1 -0
  67. package/dist/hooks/use-track-history.js +150 -0
  68. package/dist/index.d.ts +12 -0
  69. package/dist/index.d.ts.map +1 -0
  70. package/dist/index.js +17 -0
  71. package/dist/network/axios-factory.d.ts +61 -0
  72. package/dist/network/axios-factory.d.ts.map +1 -0
  73. package/dist/network/axios-factory.js +119 -0
  74. package/dist/network/index.d.ts +5 -0
  75. package/dist/network/index.d.ts.map +1 -0
  76. package/dist/network/index.js +4 -0
  77. package/dist/network/supabase-axios.d.ts +62 -0
  78. package/dist/network/supabase-axios.d.ts.map +1 -0
  79. package/dist/network/supabase-axios.js +74 -0
  80. package/dist/store/index.d.ts +2 -0
  81. package/dist/store/index.d.ts.map +1 -0
  82. package/dist/store/index.js +1 -0
  83. package/dist/store/store-access.d.ts +48 -0
  84. package/dist/store/store-access.d.ts.map +1 -0
  85. package/dist/store/store-access.js +131 -0
  86. package/dist/types/index.d.ts +58 -0
  87. package/dist/types/index.d.ts.map +1 -0
  88. package/dist/types/index.js +5 -0
  89. package/dist/types/service.d.ts +17 -0
  90. package/dist/types/service.d.ts.map +1 -0
  91. package/dist/types/service.js +31 -0
  92. package/dist/utils/index.d.ts +2 -0
  93. package/dist/utils/index.d.ts.map +1 -0
  94. package/dist/utils/index.js +1 -0
  95. package/dist/utils/storage.d.ts +27 -0
  96. package/dist/utils/storage.d.ts.map +1 -0
  97. package/dist/utils/storage.js +78 -0
  98. package/package.json +27 -0
@@ -0,0 +1,89 @@
1
+ /**
2
+ * MFA Navigate Hook - KOMCA 패턴
3
+ * 서비스 인식 네비게이션
4
+ */
5
+ import { useCallback, useMemo } from 'react';
6
+ import { useLocation, useNavigate } from 'react-router-dom';
7
+ import { storage } from '../utils/storage';
8
+ import { serviceTypeToPrefix, prefixToServiceType, getServiceFromPath } from '../types/service';
9
+ /**
10
+ * MFA Navigate Hook
11
+ * 서비스 prefix를 자동으로 처리하는 네비게이션
12
+ */
13
+ export function useMfaNavigate() {
14
+ const navigate = useNavigate();
15
+ const location = useLocation();
16
+ // 현재 서비스 타입
17
+ const currentService = useMemo(() => {
18
+ return getServiceFromPath(location.pathname);
19
+ }, [location.pathname]);
20
+ return useCallback((to, options) => {
21
+ const isHostApp = storage.isHostApp();
22
+ let pathname;
23
+ let search;
24
+ let hash;
25
+ if (typeof to === 'string') {
26
+ pathname = to;
27
+ }
28
+ else {
29
+ pathname = to.pathname || '';
30
+ search = to.search;
31
+ hash = to.hash;
32
+ }
33
+ // 서비스 prefix 결정
34
+ let targetService = options?.service || currentService;
35
+ // 이미 prefix가 있으면 그대로 사용
36
+ if (pathname.startsWith('/@') || pathname.startsWith('/')) {
37
+ // prefix가 이미 있는 경우
38
+ if (pathname.startsWith('/@')) {
39
+ const prefix = pathname.split('/')[1];
40
+ if (prefixToServiceType[prefix]) {
41
+ targetService = prefixToServiceType[prefix];
42
+ }
43
+ }
44
+ }
45
+ else {
46
+ // prefix가 없으면 현재 서비스의 prefix 추가
47
+ if (targetService) {
48
+ const prefix = serviceTypeToPrefix[targetService];
49
+ pathname = `/${prefix}${pathname.startsWith('/') ? '' : '/'}${pathname}`;
50
+ }
51
+ }
52
+ // 네비게이션 실행
53
+ navigate({ pathname, search, hash }, { ...options, service: undefined });
54
+ console.log(`[Navigate] ${pathname}${search ? `?${search}` : ''}`);
55
+ }, [navigate, currentService]);
56
+ }
57
+ /**
58
+ * 현재 위치 정보 Hook
59
+ */
60
+ export function useCurrentLocation() {
61
+ const location = useLocation();
62
+ return useMemo(() => ({
63
+ pathname: location.pathname,
64
+ search: location.search,
65
+ hash: location.hash,
66
+ state: location.state,
67
+ service: getServiceFromPath(location.pathname),
68
+ isHostApp: storage.isHostApp(),
69
+ }), [location]);
70
+ }
71
+ /**
72
+ * 경로 빌더
73
+ */
74
+ export function buildPath(pathname, service, params) {
75
+ let path = pathname;
76
+ // 서비스 prefix 추가
77
+ if (service) {
78
+ const prefix = serviceTypeToPrefix[service];
79
+ if (!pathname.startsWith(`/${prefix}`)) {
80
+ path = `/${prefix}${pathname.startsWith('/') ? '' : '/'}${pathname}`;
81
+ }
82
+ }
83
+ // 쿼리 파라미터 추가
84
+ if (params && Object.keys(params).length > 0) {
85
+ const searchParams = new URLSearchParams(params);
86
+ path += `?${searchParams.toString()}`;
87
+ }
88
+ return path;
89
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Track History Hook - KOMCA 패턴
3
+ * 라우팅 변경 감지 및 자동 탭(Recent Menu) 관리
4
+ */
5
+ import { RecentMenu } from '../types';
6
+ export interface LnbItem {
7
+ title: string;
8
+ link: string;
9
+ searchStr?: string;
10
+ children?: LnbItem[];
11
+ }
12
+ export interface TrackHistoryOptions {
13
+ lnbItems: LnbItem[];
14
+ excludePaths?: string[];
15
+ onPageView?: (pathname: string) => void;
16
+ }
17
+ /**
18
+ * Track History Hook
19
+ */
20
+ export declare function useTrackHistory(options: TrackHistoryOptions): {
21
+ loaded: boolean;
22
+ };
23
+ /**
24
+ * Recent Menu 상태 Hook
25
+ */
26
+ export declare function useRecentMenuState<D = any>(): {
27
+ list: RecentMenu[];
28
+ currentId: string;
29
+ currentMenu: RecentMenu | undefined;
30
+ data: D | undefined;
31
+ state: any;
32
+ };
33
+ //# sourceMappingURL=use-track-history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-track-history.d.ts","sourceRoot":"","sources":["../../src/hooks/use-track-history.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAItC,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;CACtB;AAGD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAkBD;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,mBAAmB;;EA0H3D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,GAAG,GAAG;;;;UAYX,CAAC,GAAG,SAAS;;EAG3C"}
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Track History Hook - KOMCA 패턴
3
+ * 라우팅 변경 감지 및 자동 탭(Recent Menu) 관리
4
+ */
5
+ import { useCallback, useEffect, useRef, useState } from 'react';
6
+ import { useLocation } from 'react-router-dom';
7
+ import { v4 as uuid } from 'uuid';
8
+ import { dispatchToHost, getHostStore } from '../store/store-access';
9
+ import { storage } from '../utils/storage';
10
+ import { getServiceFromPath } from '../types/service';
11
+ /**
12
+ * 경로에서 타이틀 찾기
13
+ */
14
+ function findTitleByPath(pathname, lnbItems) {
15
+ for (const item of lnbItems) {
16
+ if (pathname.includes(item.link) || item.link.includes(pathname)) {
17
+ return item.title;
18
+ }
19
+ if (item.children) {
20
+ const childTitle = findTitleByPath(pathname, item.children);
21
+ if (childTitle)
22
+ return childTitle;
23
+ }
24
+ }
25
+ return '';
26
+ }
27
+ /**
28
+ * Track History Hook
29
+ */
30
+ export function useTrackHistory(options) {
31
+ const location = useLocation();
32
+ const { lnbItems, excludePaths = [], onPageView } = options;
33
+ const [loaded, setLoaded] = useState(false);
34
+ const isInitRef = useRef(false);
35
+ const prevLocationRef = useRef(null);
36
+ // 페이지 정보 추가
37
+ const addPageInfo = useCallback((pathname, search, state) => {
38
+ const store = getHostStore();
39
+ if (!store)
40
+ return;
41
+ const recentMenuState = store.getState().recentMenu;
42
+ const list = recentMenuState?.list || [];
43
+ // 이미 존재하는 메뉴인지 확인
44
+ const existingIndex = list.findIndex((item) => item.pathname === pathname);
45
+ const service = getServiceFromPath(pathname);
46
+ if (existingIndex === -1) {
47
+ // 새 메뉴 추가
48
+ const id = uuid();
49
+ const title = findTitleByPath(pathname, lnbItems) || pathname.split('/').pop() || '페이지';
50
+ dispatchToHost({
51
+ type: 'recentMenu/addRecentMenu',
52
+ payload: {
53
+ id,
54
+ pathname,
55
+ search,
56
+ title,
57
+ service,
58
+ data: state,
59
+ },
60
+ });
61
+ dispatchToHost({ type: 'recentMenu/setCurrentMenuId', payload: id });
62
+ }
63
+ else {
64
+ // 기존 메뉴 활성화
65
+ const existingMenu = list[existingIndex];
66
+ dispatchToHost({ type: 'recentMenu/setCurrentMenuId', payload: existingMenu.id });
67
+ // 검색 파라미터 업데이트
68
+ if (search !== existingMenu.search) {
69
+ dispatchToHost({
70
+ type: 'recentMenu/updateMenuSearch',
71
+ payload: { id: existingMenu.id, search },
72
+ });
73
+ }
74
+ // 데이터 업데이트
75
+ if (state) {
76
+ dispatchToHost({
77
+ type: 'recentMenu/updateMenuData',
78
+ payload: { id: existingMenu.id, data: state },
79
+ });
80
+ }
81
+ }
82
+ // 서비스 타입 업데이트
83
+ if (service) {
84
+ dispatchToHost({ type: 'app/setService', payload: service });
85
+ }
86
+ }, [lnbItems]);
87
+ // 초기화
88
+ useEffect(() => {
89
+ if (isInitRef.current)
90
+ return;
91
+ isInitRef.current = true;
92
+ // Storage에서 Recent Menu 복구
93
+ const savedList = storage.getRecentMenu();
94
+ if (savedList.length > 0) {
95
+ dispatchToHost({
96
+ type: 'recentMenu/setRecentMenu',
97
+ payload: { list: savedList },
98
+ });
99
+ }
100
+ // 현재 페이지 추가
101
+ const { pathname, search } = window.location;
102
+ if (!excludePaths.some(path => pathname.startsWith(path))) {
103
+ addPageInfo(pathname, search);
104
+ }
105
+ prevLocationRef.current = { pathname, search };
106
+ setLoaded(true);
107
+ console.log('[TrackHistory] 초기화 완료:', pathname);
108
+ }, [addPageInfo, excludePaths]);
109
+ // 라우팅 변경 감지
110
+ useEffect(() => {
111
+ if (!loaded)
112
+ return;
113
+ const { pathname, search, state } = location;
114
+ const prev = prevLocationRef.current;
115
+ // 같은 페이지면 무시
116
+ if (prev && prev.pathname === pathname && prev.search === search) {
117
+ return;
118
+ }
119
+ // 제외 경로면 무시
120
+ if (excludePaths.some(path => pathname.startsWith(path))) {
121
+ prevLocationRef.current = { pathname, search };
122
+ return;
123
+ }
124
+ // 모달 상태면 무시
125
+ if (state === 'modal') {
126
+ prevLocationRef.current = { pathname, search };
127
+ return;
128
+ }
129
+ addPageInfo(pathname, search, state);
130
+ onPageView?.(pathname);
131
+ prevLocationRef.current = { pathname, search };
132
+ console.log('[TrackHistory] 페이지 변경:', pathname);
133
+ }, [location, loaded, addPageInfo, excludePaths, onPageView]);
134
+ return { loaded };
135
+ }
136
+ /**
137
+ * Recent Menu 상태 Hook
138
+ */
139
+ export function useRecentMenuState() {
140
+ const store = getHostStore();
141
+ const state = store?.getState().recentMenu;
142
+ const currentMenu = state?.list?.find((menu) => menu.id === state?.currentId);
143
+ return {
144
+ list: state?.list || [],
145
+ currentId: state?.currentId || '',
146
+ currentMenu,
147
+ data: currentMenu?.data,
148
+ state: currentMenu?.state,
149
+ };
150
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @mfa/lib - MFA 공통 라이브러리
3
+ *
4
+ * KOMCA 패턴 기반 마이크로프론트엔드 공통 유틸리티
5
+ */
6
+ export * from './types';
7
+ export * from './store';
8
+ export * from './utils';
9
+ export * from './network';
10
+ export * from './hooks';
11
+ export * from './components';
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,cAAc,SAAS,CAAC;AAGxB,cAAc,SAAS,CAAC;AAGxB,cAAc,SAAS,CAAC;AAGxB,cAAc,WAAW,CAAC;AAG1B,cAAc,SAAS,CAAC;AAGxB,cAAc,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @mfa/lib - MFA 공통 라이브러리
3
+ *
4
+ * KOMCA 패턴 기반 마이크로프론트엔드 공통 유틸리티
5
+ */
6
+ // Types
7
+ export * from './types';
8
+ // Store Access
9
+ export * from './store';
10
+ // Utils
11
+ export * from './utils';
12
+ // Network (Axios Factory)
13
+ export * from './network';
14
+ // Hooks
15
+ export * from './hooks';
16
+ // Components
17
+ export * from './components';
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Axios Factory - KOMCA 패턴
3
+ * 401 에러시 자동 토큰 갱신 포함
4
+ */
5
+ import { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
6
+ export type RequestConfig = Omit<AxiosRequestConfig, 'headers'> & {
7
+ headers: Record<string, string>;
8
+ _isRetry?: boolean;
9
+ };
10
+ export type Response<ResData> = AxiosResponse<ResData>;
11
+ export interface AxiosConfig extends AxiosRequestConfig {
12
+ hostUrl?: string;
13
+ basePath?: string;
14
+ }
15
+ export type ErrorDetailCodeType = 'TYPE_MISMATCH' | 'NotBlank' | 'NotNull' | 'Pattern' | 'Min' | 'Max' | 'Size';
16
+ export interface ErrorDetail {
17
+ code: ErrorDetailCodeType;
18
+ field?: string;
19
+ message?: string;
20
+ }
21
+ export interface ApiErrorResponse {
22
+ code: string;
23
+ statusCode: number;
24
+ timestamp: string;
25
+ errorDetails?: ErrorDetail[];
26
+ }
27
+ export interface ExtendedAxiosError extends AxiosError {
28
+ response?: AxiosResponse<ApiErrorResponse>;
29
+ }
30
+ export declare function isApiError(error: any): error is ExtendedAxiosError;
31
+ export declare function hasErrorDetails(error: any): ErrorDetail[] | undefined;
32
+ export declare function isAxiosError(error: any): error is AxiosError;
33
+ export interface ServiceConfig {
34
+ hostUrl: string;
35
+ basePath?: string;
36
+ timeout?: number;
37
+ }
38
+ export type RefreshTokenFn = () => Promise<string | null>;
39
+ export type DispatchErrorFn = (errorDetails: ErrorDetail[]) => void;
40
+ export interface FactoryConfig {
41
+ getAccessToken: () => string;
42
+ setAccessToken: (token: string) => void;
43
+ refreshToken?: RefreshTokenFn;
44
+ onError?: DispatchErrorFn;
45
+ onUnauthorized?: () => void;
46
+ }
47
+ export declare function initAxiosFactory(config: FactoryConfig): void;
48
+ /**
49
+ * Axios Client Factory
50
+ */
51
+ export declare class AxiosClientFactory {
52
+ /**
53
+ * 기본 요청 핸들러
54
+ */
55
+ private static defaultRequestHandler;
56
+ /**
57
+ * Axios 클라이언트 생성
58
+ */
59
+ static createClient(serviceConfig: AxiosConfig, customRequestHandler?: (config: RequestConfig) => Promise<RequestConfig> | RequestConfig): AxiosInstance;
60
+ }
61
+ //# sourceMappingURL=axios-factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"axios-factory.d.ts","sourceRoot":"","sources":["../../src/network/axios-factory.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAc,EAAE,UAAU,EAAE,aAAa,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAG5F,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,kBAAkB,EAAE,SAAS,CAAC,GAAG;IAChE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,QAAQ,CAAC,OAAO,IAAI,aAAa,CAAC,OAAO,CAAC,CAAC;AAEvD,MAAM,WAAW,WAAY,SAAQ,kBAAkB;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAGD,MAAM,MAAM,mBAAmB,GAAG,eAAe,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;AAGhH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAGD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,WAAW,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,kBAAmB,SAAQ,UAAU;IACpD,QAAQ,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;CAC5C;AAGD,wBAAgB,UAAU,CAAC,KAAK,EAAE,GAAG,GAAG,KAAK,IAAI,kBAAkB,CAOlE;AAGD,wBAAgB,eAAe,CAAC,KAAK,EAAE,GAAG,GAAG,WAAW,EAAE,GAAG,SAAS,CAKrE;AAGD,wBAAgB,YAAY,CAAC,KAAK,EAAE,GAAG,GAAG,KAAK,IAAI,UAAU,CAE5D;AAGD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAGD,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAG1D,MAAM,MAAM,eAAe,GAAG,CAAC,YAAY,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC;AAGpE,MAAM,WAAW,aAAa;IAC5B,cAAc,EAAE,MAAM,MAAM,CAAC;IAC7B,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,YAAY,CAAC,EAAE,cAAc,CAAC;IAC9B,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AAKD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,aAAa,QAErD;AAED;;GAEG;AACH,qBAAa,kBAAkB;IAC7B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,qBAAqB,CAElC;IAEF;;OAEG;IACH,MAAM,CAAC,YAAY,CACjB,aAAa,EAAE,WAAW,EAC1B,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC,aAAa,CAAC,GAAG,aAAa,GACvF,aAAa;CA0FjB"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Axios Factory - KOMCA 패턴
3
+ * 401 에러시 자동 토큰 갱신 포함
4
+ */
5
+ var _a;
6
+ import Axios from 'axios';
7
+ import { v4 as uuid } from 'uuid';
8
+ // API 에러 여부 확인
9
+ export function isApiError(error) {
10
+ const data = error.response?.data;
11
+ return (isAxiosError(error) &&
12
+ data?.code !== undefined &&
13
+ data?.statusCode !== undefined);
14
+ }
15
+ // 에러 상세 정보 확인
16
+ export function hasErrorDetails(error) {
17
+ if (isApiError(error) && error.response?.data?.errorDetails?.length) {
18
+ return error.response.data.errorDetails;
19
+ }
20
+ return undefined;
21
+ }
22
+ // Axios 에러 여부 확인
23
+ export function isAxiosError(error) {
24
+ return Boolean(error.isAxiosError);
25
+ }
26
+ let factoryConfig = null;
27
+ // Factory 초기화
28
+ export function initAxiosFactory(config) {
29
+ factoryConfig = config;
30
+ }
31
+ /**
32
+ * Axios Client Factory
33
+ */
34
+ export class AxiosClientFactory {
35
+ /**
36
+ * Axios 클라이언트 생성
37
+ */
38
+ static createClient(serviceConfig, customRequestHandler) {
39
+ const axiosInstance = Axios.create({
40
+ baseURL: `${serviceConfig.hostUrl || ''}${serviceConfig.basePath || ''}`,
41
+ timeout: serviceConfig.timeout || 60000,
42
+ ...serviceConfig,
43
+ });
44
+ // 요청 인터셉터
45
+ axiosInstance.interceptors.request.use((config) => {
46
+ config.headers = config.headers || {};
47
+ // Access Token 추가
48
+ if (factoryConfig) {
49
+ const token = factoryConfig.getAccessToken();
50
+ if (token) {
51
+ config.headers['Authorization'] = `Bearer ${token}`;
52
+ }
53
+ }
54
+ // UUID 추가 (요청 추적용)
55
+ config.headers['X-Request-ID'] = uuid();
56
+ // 빈 값 필터링
57
+ if (config.params) {
58
+ config.params = Object.entries(config.params).reduce((acc, [key, value]) => {
59
+ if (value !== '' && value != null) {
60
+ acc[key] = value;
61
+ }
62
+ return acc;
63
+ }, {});
64
+ }
65
+ // 커스텀 요청 핸들러 실행
66
+ if (customRequestHandler) {
67
+ customRequestHandler(config);
68
+ }
69
+ return config;
70
+ });
71
+ // 응답 인터셉터
72
+ axiosInstance.interceptors.response.use((response) => response, async (error) => {
73
+ // 네트워크 에러
74
+ if (error.message === 'Network Error') {
75
+ console.error('[Network Error] 네트워크 연결을 확인해주세요.');
76
+ }
77
+ // 401 에러 - 토큰 갱신 시도
78
+ if (error.response?.status === 401 && factoryConfig?.refreshToken) {
79
+ const originalRequest = error.config;
80
+ if (!originalRequest._isRetry) {
81
+ originalRequest._isRetry = true;
82
+ try {
83
+ // refresh 엔드포인트가 아닌 경우에만 갱신 시도
84
+ if (!originalRequest.url?.includes('/auth/refresh')) {
85
+ const newToken = await factoryConfig.refreshToken();
86
+ if (newToken) {
87
+ factoryConfig.setAccessToken(newToken);
88
+ originalRequest.headers['Authorization'] = `Bearer ${newToken}`;
89
+ console.log('[Token Refresh] 토큰이 갱신되었습니다.');
90
+ return axiosInstance(originalRequest);
91
+ }
92
+ }
93
+ }
94
+ catch (refreshError) {
95
+ console.error('[Token Refresh Failed]', refreshError);
96
+ factoryConfig.setAccessToken('');
97
+ factoryConfig.onUnauthorized?.();
98
+ }
99
+ }
100
+ }
101
+ // API 에러 처리
102
+ if (isApiError(error)) {
103
+ const errorDetails = hasErrorDetails(error);
104
+ if (errorDetails && factoryConfig?.onError) {
105
+ factoryConfig.onError(errorDetails);
106
+ }
107
+ }
108
+ return Promise.reject(error);
109
+ });
110
+ return axiosInstance;
111
+ }
112
+ }
113
+ _a = AxiosClientFactory;
114
+ /**
115
+ * 기본 요청 핸들러
116
+ */
117
+ AxiosClientFactory.defaultRequestHandler = async (config) => {
118
+ return config;
119
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Network 모듈 - KOMCA 패턴
3
+ */
4
+ export * from './axios-factory';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/network/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Network 모듈 - KOMCA 패턴
3
+ */
4
+ export * from './axios-factory';
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Supabase Axios Client
3
+ * Supabase REST API를 axios로 호출하기 위한 클라이언트
4
+ * 토큰 갱신 지원
5
+ */
6
+ import { AxiosInstance } from 'axios';
7
+ /**
8
+ * Supabase Axios 설정
9
+ */
10
+ export interface SupabaseAxiosConfig {
11
+ /** Supabase 프로젝트 URL */
12
+ supabaseUrl: string;
13
+ /** Supabase anon key */
14
+ supabaseAnonKey: string;
15
+ /** 타임아웃 (기본: 30000ms) */
16
+ timeout?: number;
17
+ /** 토큰 갱신 함수 (Supabase Auth 사용 시) */
18
+ refreshSession?: () => Promise<{
19
+ accessToken: string;
20
+ refreshToken: string;
21
+ } | null>;
22
+ /** 액세스 토큰 가져오기 */
23
+ getAccessToken?: () => string;
24
+ /** 액세스 토큰 설정 */
25
+ setAccessToken?: (token: string) => void;
26
+ /** 인증 실패 시 콜백 */
27
+ onUnauthorized?: () => void;
28
+ }
29
+ /**
30
+ * Supabase Axios 클라이언트 생성
31
+ */
32
+ export declare function createSupabaseAxiosClient(config: SupabaseAxiosConfig): AxiosInstance;
33
+ /**
34
+ * Supabase Auth API 클라이언트 생성 (토큰 갱신용)
35
+ */
36
+ export declare function createSupabaseAuthClient(config: SupabaseAxiosConfig): AxiosInstance;
37
+ /**
38
+ * Supabase 토큰 갱신 헬퍼
39
+ */
40
+ export declare function refreshSupabaseToken(authClient: AxiosInstance, refreshToken: string): Promise<{
41
+ accessToken: string;
42
+ refreshToken: string;
43
+ } | null>;
44
+ /**
45
+ * API 응답 래퍼 타입
46
+ */
47
+ export interface ApiResponse<T> {
48
+ success: boolean;
49
+ data?: T;
50
+ error?: string;
51
+ }
52
+ /**
53
+ * 페이지네이션 응답 타입
54
+ */
55
+ export interface PageListResponse<T> {
56
+ data: T[];
57
+ total: number;
58
+ page: number;
59
+ limit: number;
60
+ totalPages: number;
61
+ }
62
+ //# sourceMappingURL=supabase-axios.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supabase-axios.d.ts","sourceRoot":"","sources":["../../src/network/supabase-axios.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAEtC;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,wBAAwB;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,wBAAwB;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,yBAAyB;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IACrF,kBAAkB;IAClB,cAAc,CAAC,EAAE,MAAM,MAAM,CAAC;IAC9B,gBAAgB;IAChB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,iBAAiB;IACjB,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,mBAAmB,GAAG,aAAa,CAyCpF;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,mBAAmB,GAAG,aAAa,CAenF;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,aAAa,EACzB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAkB/D;AAED;;GAEG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Supabase Axios Client
3
+ * Supabase REST API를 axios로 호출하기 위한 클라이언트
4
+ * 토큰 갱신 지원
5
+ */
6
+ import { AxiosClientFactory, initAxiosFactory } from './axios-factory';
7
+ /**
8
+ * Supabase Axios 클라이언트 생성
9
+ */
10
+ export function createSupabaseAxiosClient(config) {
11
+ const { supabaseUrl, supabaseAnonKey, timeout = 30000, refreshSession, getAccessToken, setAccessToken, onUnauthorized, } = config;
12
+ // Axios Factory 초기화 (토큰 갱신 지원)
13
+ if (getAccessToken && setAccessToken) {
14
+ initAxiosFactory({
15
+ getAccessToken,
16
+ setAccessToken,
17
+ refreshToken: refreshSession
18
+ ? async () => {
19
+ const session = await refreshSession();
20
+ return session?.accessToken || null;
21
+ }
22
+ : undefined,
23
+ onUnauthorized,
24
+ });
25
+ }
26
+ // Supabase REST API 클라이언트 생성
27
+ return AxiosClientFactory.createClient({
28
+ hostUrl: supabaseUrl,
29
+ basePath: '/rest/v1',
30
+ timeout,
31
+ }, (requestConfig) => {
32
+ // Supabase 필수 헤더
33
+ requestConfig.headers['apikey'] = supabaseAnonKey;
34
+ requestConfig.headers['Content-Type'] = 'application/json';
35
+ requestConfig.headers['Prefer'] = 'return=representation';
36
+ return requestConfig;
37
+ });
38
+ }
39
+ /**
40
+ * Supabase Auth API 클라이언트 생성 (토큰 갱신용)
41
+ */
42
+ export function createSupabaseAuthClient(config) {
43
+ const { supabaseUrl, supabaseAnonKey, timeout = 30000 } = config;
44
+ return AxiosClientFactory.createClient({
45
+ hostUrl: supabaseUrl,
46
+ basePath: '/auth/v1',
47
+ timeout,
48
+ }, (requestConfig) => {
49
+ requestConfig.headers['apikey'] = supabaseAnonKey;
50
+ requestConfig.headers['Content-Type'] = 'application/json';
51
+ return requestConfig;
52
+ });
53
+ }
54
+ /**
55
+ * Supabase 토큰 갱신 헬퍼
56
+ */
57
+ export async function refreshSupabaseToken(authClient, refreshToken) {
58
+ try {
59
+ const response = await authClient.post('/token?grant_type=refresh_token', {
60
+ refresh_token: refreshToken,
61
+ });
62
+ if (response.data?.access_token) {
63
+ return {
64
+ accessToken: response.data.access_token,
65
+ refreshToken: response.data.refresh_token || refreshToken,
66
+ };
67
+ }
68
+ return null;
69
+ }
70
+ catch (error) {
71
+ console.error('[Supabase Token Refresh Failed]', error);
72
+ return null;
73
+ }
74
+ }
@@ -0,0 +1,2 @@
1
+ export * from './store-access';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/store/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1 @@
1
+ export * from './store-access';