444 lines
12 KiB
TypeScript
444 lines
12 KiB
TypeScript
|
|
/**
|
||
|
|
* 레거시 CustomEvent ↔ V2 EventBus 어댑터
|
||
|
|
*
|
||
|
|
* 기존 window.dispatchEvent/addEventListener 기반 이벤트를
|
||
|
|
* V2 EventBus로 브릿지하여 점진적 마이그레이션을 지원합니다.
|
||
|
|
*
|
||
|
|
* 특징:
|
||
|
|
* - 양방향 브릿지 (레거시 → V2, V2 → 레거시)
|
||
|
|
* - 이벤트 중복 방지
|
||
|
|
* - 선택적 브릿지 활성화/비활성화
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { v2EventBus, V2_EVENTS, V2EventName, V2EventPayloadMap } from "../events";
|
||
|
|
|
||
|
|
// ============================================================================
|
||
|
|
// 이벤트 매핑 정의
|
||
|
|
// ============================================================================
|
||
|
|
|
||
|
|
interface EventMapping {
|
||
|
|
legacy: string;
|
||
|
|
v2: V2EventName;
|
||
|
|
/** 레거시 → V2 페이로드 변환 함수 */
|
||
|
|
toV2?: (legacyDetail: any) => any;
|
||
|
|
/** V2 → 레거시 페이로드 변환 함수 */
|
||
|
|
toLegacy?: (v2Payload: any) => any;
|
||
|
|
}
|
||
|
|
|
||
|
|
const EVENT_MAPPINGS: EventMapping[] = [
|
||
|
|
// 테이블 관련
|
||
|
|
{
|
||
|
|
legacy: "refreshTable",
|
||
|
|
v2: V2_EVENTS.TABLE_REFRESH,
|
||
|
|
toV2: (detail) => ({
|
||
|
|
tableName: detail?.tableName,
|
||
|
|
target: detail?.target ?? "all",
|
||
|
|
screenCode: detail?.screenCode,
|
||
|
|
}),
|
||
|
|
toLegacy: (payload) => ({
|
||
|
|
tableName: payload.tableName,
|
||
|
|
target: payload.target,
|
||
|
|
screenCode: payload.screenCode,
|
||
|
|
}),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
legacy: "tableListDataChange",
|
||
|
|
v2: V2_EVENTS.TABLE_DATA_CHANGE,
|
||
|
|
toV2: (detail) => ({
|
||
|
|
tableName: detail?.tableName,
|
||
|
|
data: detail?.data ?? [],
|
||
|
|
totalCount: detail?.totalCount ?? 0,
|
||
|
|
source: detail?.source ?? "legacy",
|
||
|
|
}),
|
||
|
|
toLegacy: (payload) => ({
|
||
|
|
tableName: payload.tableName,
|
||
|
|
data: payload.data,
|
||
|
|
totalCount: payload.totalCount,
|
||
|
|
source: payload.source,
|
||
|
|
}),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
legacy: "tableSelectionChange",
|
||
|
|
v2: V2_EVENTS.TABLE_SELECTION_CHANGE,
|
||
|
|
toV2: (detail) => ({
|
||
|
|
tableName: detail?.tableName,
|
||
|
|
selectedRows: detail?.selectedRows ?? [],
|
||
|
|
selectedRowIds: detail?.selectedRowIds ?? [],
|
||
|
|
source: detail?.source ?? "legacy",
|
||
|
|
}),
|
||
|
|
toLegacy: (payload) => ({
|
||
|
|
tableName: payload.tableName,
|
||
|
|
selectedRows: payload.selectedRows,
|
||
|
|
selectedRowIds: payload.selectedRowIds,
|
||
|
|
source: payload.source,
|
||
|
|
}),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
legacy: "selectionChange",
|
||
|
|
v2: V2_EVENTS.TABLE_SELECTION_CHANGE,
|
||
|
|
toV2: (detail) => ({
|
||
|
|
tableName: detail?.tableName ?? "",
|
||
|
|
selectedRows: detail?.selectedRows ?? [],
|
||
|
|
selectedRowIds: detail?.selectedRowIds ?? [],
|
||
|
|
source: "legacy-selectionChange",
|
||
|
|
}),
|
||
|
|
toLegacy: (payload) => ({
|
||
|
|
tableName: payload.tableName,
|
||
|
|
selectedRows: payload.selectedRows,
|
||
|
|
selectedRowIds: payload.selectedRowIds,
|
||
|
|
source: payload.source,
|
||
|
|
}),
|
||
|
|
},
|
||
|
|
|
||
|
|
// 폼 저장 관련
|
||
|
|
{
|
||
|
|
legacy: "beforeFormSave",
|
||
|
|
v2: V2_EVENTS.FORM_SAVE_COLLECT,
|
||
|
|
toV2: (detail) => ({
|
||
|
|
requestId: detail?.requestId ?? `req_${Date.now()}`,
|
||
|
|
formData: detail?.formData ?? {},
|
||
|
|
componentId: detail?.componentId ?? "legacy",
|
||
|
|
}),
|
||
|
|
toLegacy: (payload) => ({
|
||
|
|
requestId: payload.requestId,
|
||
|
|
formData: payload.formData,
|
||
|
|
componentId: payload.componentId,
|
||
|
|
}),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
legacy: "saveSuccess",
|
||
|
|
v2: V2_EVENTS.FORM_SAVE_COMPLETE,
|
||
|
|
toV2: (detail) => ({
|
||
|
|
requestId: detail?.requestId ?? "",
|
||
|
|
success: true,
|
||
|
|
savedData: detail?.savedData ?? detail,
|
||
|
|
tableName: detail?.tableName ?? "",
|
||
|
|
}),
|
||
|
|
toLegacy: (payload) => ({
|
||
|
|
requestId: payload.requestId,
|
||
|
|
savedData: payload.savedData,
|
||
|
|
tableName: payload.tableName,
|
||
|
|
}),
|
||
|
|
},
|
||
|
|
|
||
|
|
// 리피터 관련
|
||
|
|
{
|
||
|
|
legacy: "repeaterSave",
|
||
|
|
v2: V2_EVENTS.REPEATER_SAVE,
|
||
|
|
toV2: (detail) => ({
|
||
|
|
repeaterId: detail?.repeaterId ?? "",
|
||
|
|
tableName: detail?.tableName ?? "",
|
||
|
|
items: detail?.items ?? [],
|
||
|
|
joinData: detail?.joinData,
|
||
|
|
}),
|
||
|
|
toLegacy: (payload) => ({
|
||
|
|
repeaterId: payload.repeaterId,
|
||
|
|
tableName: payload.tableName,
|
||
|
|
items: payload.items,
|
||
|
|
joinData: payload.joinData,
|
||
|
|
}),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
legacy: "repeaterDataChange",
|
||
|
|
v2: V2_EVENTS.REPEATER_DATA_CHANGE,
|
||
|
|
toV2: (detail) => ({
|
||
|
|
repeaterId: detail?.repeaterId ?? "",
|
||
|
|
tableName: detail?.tableName ?? "",
|
||
|
|
data: detail?.data ?? [],
|
||
|
|
action: detail?.action ?? "update",
|
||
|
|
}),
|
||
|
|
toLegacy: (payload) => ({
|
||
|
|
repeaterId: payload.repeaterId,
|
||
|
|
tableName: payload.tableName,
|
||
|
|
data: payload.data,
|
||
|
|
action: payload.action,
|
||
|
|
}),
|
||
|
|
},
|
||
|
|
|
||
|
|
// 모달 관련
|
||
|
|
{
|
||
|
|
legacy: "closeEditModal",
|
||
|
|
v2: V2_EVENTS.MODAL_CLOSE,
|
||
|
|
toV2: (detail) => ({
|
||
|
|
modalId: detail?.modalId ?? "edit-modal",
|
||
|
|
reason: detail?.reason ?? "close",
|
||
|
|
}),
|
||
|
|
toLegacy: (payload) => ({
|
||
|
|
modalId: payload.modalId,
|
||
|
|
reason: payload.reason,
|
||
|
|
}),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
legacy: "saveSuccessInModal",
|
||
|
|
v2: V2_EVENTS.MODAL_SAVE_SUCCESS,
|
||
|
|
toV2: (detail) => ({
|
||
|
|
modalId: detail?.modalId ?? "edit-modal",
|
||
|
|
savedData: detail?.savedData ?? {},
|
||
|
|
tableName: detail?.tableName ?? "",
|
||
|
|
}),
|
||
|
|
toLegacy: (payload) => ({
|
||
|
|
modalId: payload.modalId,
|
||
|
|
savedData: payload.savedData,
|
||
|
|
tableName: payload.tableName,
|
||
|
|
}),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
legacy: "openScreenModal",
|
||
|
|
v2: V2_EVENTS.MODAL_OPEN,
|
||
|
|
toV2: (detail) => ({
|
||
|
|
modalId: detail?.modalId ?? "",
|
||
|
|
screenCode: detail?.screenCode,
|
||
|
|
data: detail?.data,
|
||
|
|
mode: detail?.mode ?? "view",
|
||
|
|
}),
|
||
|
|
toLegacy: (payload) => ({
|
||
|
|
modalId: payload.modalId,
|
||
|
|
screenCode: payload.screenCode,
|
||
|
|
data: payload.data,
|
||
|
|
mode: payload.mode,
|
||
|
|
}),
|
||
|
|
},
|
||
|
|
|
||
|
|
// 카드 디스플레이
|
||
|
|
{
|
||
|
|
legacy: "refreshCardDisplay",
|
||
|
|
v2: V2_EVENTS.CARD_REFRESH,
|
||
|
|
toV2: (detail) => ({
|
||
|
|
cardId: detail?.cardId,
|
||
|
|
tableName: detail?.tableName,
|
||
|
|
}),
|
||
|
|
toLegacy: (payload) => ({
|
||
|
|
cardId: payload.cardId,
|
||
|
|
tableName: payload.tableName,
|
||
|
|
}),
|
||
|
|
},
|
||
|
|
|
||
|
|
// 분할 패널
|
||
|
|
{
|
||
|
|
legacy: "splitPanelDataTransfer",
|
||
|
|
v2: V2_EVENTS.SPLIT_PANEL_DATA_TRANSFER,
|
||
|
|
toV2: (detail) => ({
|
||
|
|
sourcePanel: detail?.sourcePanel ?? "left",
|
||
|
|
targetPanel: detail?.targetPanel ?? "right",
|
||
|
|
data: detail?.data ?? {},
|
||
|
|
tableName: detail?.tableName ?? "",
|
||
|
|
}),
|
||
|
|
toLegacy: (payload) => ({
|
||
|
|
sourcePanel: payload.sourcePanel,
|
||
|
|
targetPanel: payload.targetPanel,
|
||
|
|
data: payload.data,
|
||
|
|
tableName: payload.tableName,
|
||
|
|
}),
|
||
|
|
},
|
||
|
|
|
||
|
|
// 컴포넌트 데이터 전송
|
||
|
|
{
|
||
|
|
legacy: "componentDataTransfer",
|
||
|
|
v2: V2_EVENTS.COMPONENT_DATA_TRANSFER,
|
||
|
|
toV2: (detail) => ({
|
||
|
|
sourceComponentId: detail?.sourceComponentId ?? "",
|
||
|
|
targetComponentId: detail?.targetComponentId,
|
||
|
|
data: detail?.data ?? {},
|
||
|
|
tableName: detail?.tableName,
|
||
|
|
}),
|
||
|
|
toLegacy: (payload) => ({
|
||
|
|
sourceComponentId: payload.sourceComponentId,
|
||
|
|
targetComponentId: payload.targetComponentId,
|
||
|
|
data: payload.data,
|
||
|
|
tableName: payload.tableName,
|
||
|
|
}),
|
||
|
|
},
|
||
|
|
|
||
|
|
// 관련 버튼
|
||
|
|
{
|
||
|
|
legacy: "related-button-register",
|
||
|
|
v2: V2_EVENTS.RELATED_BUTTON_REGISTER,
|
||
|
|
toV2: (detail) => ({
|
||
|
|
buttonId: detail?.buttonId ?? "",
|
||
|
|
targetTables: detail?.targetTables ?? [],
|
||
|
|
}),
|
||
|
|
toLegacy: (payload) => ({
|
||
|
|
buttonId: payload.buttonId,
|
||
|
|
targetTables: payload.targetTables,
|
||
|
|
}),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
legacy: "related-button-unregister",
|
||
|
|
v2: V2_EVENTS.RELATED_BUTTON_UNREGISTER,
|
||
|
|
toV2: (detail) => ({
|
||
|
|
buttonId: detail?.buttonId ?? "",
|
||
|
|
}),
|
||
|
|
toLegacy: (payload) => ({
|
||
|
|
buttonId: payload.buttonId,
|
||
|
|
}),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
legacy: "related-button-select",
|
||
|
|
v2: V2_EVENTS.RELATED_BUTTON_SELECT,
|
||
|
|
toV2: (detail) => ({
|
||
|
|
tableName: detail?.tableName ?? "",
|
||
|
|
selectedData: detail?.selectedData ?? [],
|
||
|
|
}),
|
||
|
|
toLegacy: (payload) => ({
|
||
|
|
tableName: payload.tableName,
|
||
|
|
selectedData: payload.selectedData,
|
||
|
|
}),
|
||
|
|
},
|
||
|
|
];
|
||
|
|
|
||
|
|
// ============================================================================
|
||
|
|
// 어댑터 클래스
|
||
|
|
// ============================================================================
|
||
|
|
|
||
|
|
class LegacyEventAdapter {
|
||
|
|
private isActive = false;
|
||
|
|
private legacyListeners: Map<string, (e: Event) => void> = new Map();
|
||
|
|
private v2Unsubscribes: Map<string, () => void> = new Map();
|
||
|
|
|
||
|
|
/** 브릿지에서 발생한 이벤트 추적 (무한 루프 방지) */
|
||
|
|
private bridgedEvents: Set<string> = new Set();
|
||
|
|
|
||
|
|
/** 브릿지 방향 설정 */
|
||
|
|
private config = {
|
||
|
|
legacyToV2: true,
|
||
|
|
v2ToLegacy: true,
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 어댑터 초기화 및 브릿지 시작
|
||
|
|
*
|
||
|
|
* @param options - 브릿지 설정
|
||
|
|
*/
|
||
|
|
init(options?: { legacyToV2?: boolean; v2ToLegacy?: boolean }): void {
|
||
|
|
if (this.isActive) {
|
||
|
|
console.warn("[LegacyEventAdapter] 이미 초기화되어 있습니다.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (options) {
|
||
|
|
this.config = { ...this.config, ...options };
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log("[LegacyEventAdapter] 초기화 시작", this.config);
|
||
|
|
|
||
|
|
EVENT_MAPPINGS.forEach((mapping) => {
|
||
|
|
// 레거시 → V2 브릿지
|
||
|
|
if (this.config.legacyToV2) {
|
||
|
|
this.setupLegacyToV2Bridge(mapping);
|
||
|
|
}
|
||
|
|
|
||
|
|
// V2 → 레거시 브릿지
|
||
|
|
if (this.config.v2ToLegacy) {
|
||
|
|
this.setupV2ToLegacyBridge(mapping);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
this.isActive = true;
|
||
|
|
console.log(
|
||
|
|
`[LegacyEventAdapter] 초기화 완료 (${EVENT_MAPPINGS.length}개 매핑)`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
private setupLegacyToV2Bridge(mapping: EventMapping): void {
|
||
|
|
const listener = (event: Event) => {
|
||
|
|
const customEvent = event as CustomEvent;
|
||
|
|
const bridgeKey = `${mapping.legacy}-${Date.now()}`;
|
||
|
|
|
||
|
|
// 무한 루프 방지: 브릿지에서 발생한 이벤트인지 확인
|
||
|
|
if (customEvent.detail?.__v2Bridged) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
this.bridgedEvents.add(bridgeKey);
|
||
|
|
|
||
|
|
// 페이로드 변환
|
||
|
|
const v2Payload = mapping.toV2
|
||
|
|
? mapping.toV2(customEvent.detail)
|
||
|
|
: customEvent.detail;
|
||
|
|
|
||
|
|
// V2 EventBus로 발행
|
||
|
|
v2EventBus.emitSync(mapping.v2, v2Payload);
|
||
|
|
|
||
|
|
// 잠시 후 브릿지 키 정리
|
||
|
|
setTimeout(() => {
|
||
|
|
this.bridgedEvents.delete(bridgeKey);
|
||
|
|
}, 100);
|
||
|
|
};
|
||
|
|
|
||
|
|
window.addEventListener(mapping.legacy, listener);
|
||
|
|
this.legacyListeners.set(mapping.legacy, listener);
|
||
|
|
}
|
||
|
|
|
||
|
|
private setupV2ToLegacyBridge(mapping: EventMapping): void {
|
||
|
|
const unsubscribe = v2EventBus.subscribe(
|
||
|
|
mapping.v2,
|
||
|
|
(payload) => {
|
||
|
|
// 무한 루프 방지 표시 추가
|
||
|
|
const legacyPayload = mapping.toLegacy
|
||
|
|
? { ...mapping.toLegacy(payload), __v2Bridged: true }
|
||
|
|
: { ...payload, __v2Bridged: true };
|
||
|
|
|
||
|
|
// 레거시 이벤트 발행
|
||
|
|
window.dispatchEvent(
|
||
|
|
new CustomEvent(mapping.legacy, { detail: legacyPayload })
|
||
|
|
);
|
||
|
|
},
|
||
|
|
{ componentId: "legacy-adapter" }
|
||
|
|
);
|
||
|
|
|
||
|
|
this.v2Unsubscribes.set(mapping.v2, unsubscribe);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 어댑터 정지 및 정리
|
||
|
|
*/
|
||
|
|
destroy(): void {
|
||
|
|
if (!this.isActive) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 레거시 리스너 정리
|
||
|
|
this.legacyListeners.forEach((listener, eventName) => {
|
||
|
|
window.removeEventListener(eventName, listener);
|
||
|
|
});
|
||
|
|
this.legacyListeners.clear();
|
||
|
|
|
||
|
|
// V2 구독 정리
|
||
|
|
this.v2Unsubscribes.forEach((unsubscribe) => {
|
||
|
|
unsubscribe();
|
||
|
|
});
|
||
|
|
this.v2Unsubscribes.clear();
|
||
|
|
|
||
|
|
this.bridgedEvents.clear();
|
||
|
|
this.isActive = false;
|
||
|
|
|
||
|
|
console.log("[LegacyEventAdapter] 정리 완료");
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 현재 활성 상태 확인
|
||
|
|
*/
|
||
|
|
get active(): boolean {
|
||
|
|
return this.isActive;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 매핑된 이벤트 목록 조회
|
||
|
|
*/
|
||
|
|
getMappings(): Array<{ legacy: string; v2: string }> {
|
||
|
|
return EVENT_MAPPINGS.map((m) => ({
|
||
|
|
legacy: m.legacy,
|
||
|
|
v2: m.v2,
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 싱글톤 인스턴스
|
||
|
|
export const legacyEventAdapter = new LegacyEventAdapter();
|
||
|
|
|
||
|
|
// 개발 환경에서 window에 노출 (디버깅용)
|
||
|
|
if (typeof window !== "undefined" && process.env.NODE_ENV === "development") {
|
||
|
|
(window as any).__legacyEventAdapter = legacyEventAdapter;
|
||
|
|
}
|
||
|
|
|