@dooboostore/simple-web-component 1.0.50 → 1.0.52
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 +735 -354
- package/dist/cjs/SwcAppEngine.js.map +2 -2
- package/dist/cjs/decorators/addEventListener.js +64 -146
- package/dist/cjs/decorators/addEventListener.js.map +2 -2
- package/dist/cjs/decorators/applyAttribute.js +36 -48
- package/dist/cjs/decorators/applyAttribute.js.map +2 -2
- package/dist/cjs/decorators/applyClass.js +9 -12
- package/dist/cjs/decorators/applyClass.js.map +2 -2
- package/dist/cjs/decorators/applyNode.js +14 -22
- package/dist/cjs/decorators/applyNode.js.map +2 -2
- package/dist/cjs/decorators/applyProperty.js +12 -42
- package/dist/cjs/decorators/applyProperty.js.map +2 -2
- package/dist/cjs/decorators/applySlot.js +3 -3
- package/dist/cjs/decorators/applySlot.js.map +2 -2
- package/dist/cjs/decorators/applyStyle.js +9 -12
- package/dist/cjs/decorators/applyStyle.js.map +2 -2
- package/dist/cjs/decorators/around.js +124 -0
- package/dist/cjs/decorators/around.js.map +7 -0
- package/dist/cjs/decorators/changedAttribute.js +40 -12
- package/dist/cjs/decorators/changedAttribute.js.map +2 -2
- package/dist/cjs/decorators/elementDefine.js +224 -195
- package/dist/cjs/decorators/elementDefine.js.map +3 -3
- package/dist/cjs/decorators/emitCustomEvent.js +14 -12
- package/dist/cjs/decorators/emitCustomEvent.js.map +2 -2
- package/dist/cjs/decorators/eventMedia.js +36 -8
- package/dist/cjs/decorators/eventMedia.js.map +2 -2
- package/dist/cjs/decorators/fetch.js +278 -0
- package/dist/cjs/decorators/fetch.js.map +7 -0
- package/dist/cjs/decorators/index.js +5 -2
- package/dist/cjs/decorators/index.js.map +2 -2
- package/dist/cjs/decorators/intersectionObserver.js +33 -13
- package/dist/cjs/decorators/intersectionObserver.js.map +2 -2
- package/dist/cjs/decorators/lifecycles.js +11 -11
- package/dist/cjs/decorators/lifecycles.js.map +2 -2
- package/dist/cjs/decorators/mutationObserver.js +55 -39
- package/dist/cjs/decorators/mutationObserver.js.map +3 -3
- package/dist/cjs/decorators/parameter.js +92 -9
- package/dist/cjs/decorators/parameter.js.map +2 -2
- package/dist/cjs/decorators/publishSwcAppMessage.js +2 -2
- package/dist/cjs/decorators/publishSwcAppMessage.js.map +2 -2
- package/dist/cjs/decorators/query.js +11 -5
- package/dist/cjs/decorators/query.js.map +2 -2
- package/dist/cjs/decorators/requestAnimationFrame.js +7 -16
- package/dist/cjs/decorators/requestAnimationFrame.js.map +2 -2
- package/dist/cjs/decorators/resizeObserver.js +41 -17
- package/dist/cjs/decorators/resizeObserver.js.map +3 -3
- package/dist/cjs/decorators/setInterval.js +36 -25
- package/dist/cjs/decorators/setInterval.js.map +2 -2
- package/dist/cjs/decorators/setTimeout.js +36 -25
- package/dist/cjs/decorators/setTimeout.js.map +2 -2
- package/dist/cjs/decorators/state.js +13 -10
- package/dist/cjs/decorators/state.js.map +2 -2
- package/dist/cjs/decorators/{subscribeSwcAppMessageWhileConnected.js → subscribeSwcAppMessage.js} +20 -21
- package/dist/cjs/decorators/subscribeSwcAppMessage.js.map +7 -0
- package/dist/cjs/decorators/{subscribeSwcAppRouteChangeWhileConnected.js → subscribeSwcAppRouteChange.js} +23 -19
- package/dist/cjs/decorators/subscribeSwcAppRouteChange.js.map +7 -0
- package/dist/cjs/decorators/swcAppRoute.js +149 -0
- package/dist/cjs/decorators/swcAppRoute.js.map +7 -0
- package/dist/cjs/elements/SwcApp.js +70 -0
- package/dist/cjs/elements/SwcApp.js.map +2 -2
- package/dist/cjs/elements/SwcAppMixin.js +262 -83
- package/dist/cjs/elements/SwcAppMixin.js.map +3 -3
- package/dist/cjs/types/index.js +5 -1
- package/dist/cjs/types/index.js.map +2 -2
- package/dist/cjs/utils/Utils.js +36 -15
- package/dist/cjs/utils/Utils.js.map +2 -2
- package/dist/esm/SwcAppEngine.js.map +2 -2
- package/dist/esm/decorators/addEventListener.js +64 -146
- package/dist/esm/decorators/addEventListener.js.map +2 -2
- package/dist/esm/decorators/applyAttribute.js +36 -48
- package/dist/esm/decorators/applyAttribute.js.map +2 -2
- package/dist/esm/decorators/applyClass.js +9 -12
- package/dist/esm/decorators/applyClass.js.map +2 -2
- package/dist/esm/decorators/applyNode.js +15 -23
- package/dist/esm/decorators/applyNode.js.map +2 -2
- package/dist/esm/decorators/applyProperty.js +12 -42
- package/dist/esm/decorators/applyProperty.js.map +2 -2
- package/dist/esm/decorators/applySlot.js +3 -3
- package/dist/esm/decorators/applySlot.js.map +2 -2
- package/dist/esm/decorators/applyStyle.js +9 -12
- package/dist/esm/decorators/applyStyle.js.map +2 -2
- package/dist/esm/decorators/around.js +105 -0
- package/dist/esm/decorators/around.js.map +7 -0
- package/dist/esm/decorators/changedAttribute.js +40 -12
- package/dist/esm/decorators/changedAttribute.js.map +2 -2
- package/dist/esm/decorators/elementDefine.js +222 -193
- package/dist/esm/decorators/elementDefine.js.map +3 -3
- package/dist/esm/decorators/emitCustomEvent.js +15 -13
- package/dist/esm/decorators/emitCustomEvent.js.map +2 -2
- package/dist/esm/decorators/eventMedia.js +36 -8
- package/dist/esm/decorators/eventMedia.js.map +2 -2
- package/dist/esm/decorators/fetch.js +259 -0
- package/dist/esm/decorators/fetch.js.map +7 -0
- package/dist/esm/decorators/index.js +5 -2
- package/dist/esm/decorators/index.js.map +2 -2
- package/dist/esm/decorators/intersectionObserver.js +33 -13
- package/dist/esm/decorators/intersectionObserver.js.map +2 -2
- package/dist/esm/decorators/lifecycles.js +11 -11
- package/dist/esm/decorators/lifecycles.js.map +2 -2
- package/dist/esm/decorators/mutationObserver.js +55 -39
- package/dist/esm/decorators/mutationObserver.js.map +3 -3
- package/dist/esm/decorators/parameter.js +92 -9
- package/dist/esm/decorators/parameter.js.map +2 -2
- package/dist/esm/decorators/publishSwcAppMessage.js +2 -2
- package/dist/esm/decorators/publishSwcAppMessage.js.map +2 -2
- package/dist/esm/decorators/query.js +11 -5
- package/dist/esm/decorators/query.js.map +2 -2
- package/dist/esm/decorators/requestAnimationFrame.js +8 -17
- package/dist/esm/decorators/requestAnimationFrame.js.map +2 -2
- package/dist/esm/decorators/resizeObserver.js +41 -17
- package/dist/esm/decorators/resizeObserver.js.map +2 -2
- package/dist/esm/decorators/setInterval.js +37 -26
- package/dist/esm/decorators/setInterval.js.map +2 -2
- package/dist/esm/decorators/setTimeout.js +37 -26
- package/dist/esm/decorators/setTimeout.js.map +2 -2
- package/dist/esm/decorators/state.js +13 -10
- package/dist/esm/decorators/state.js.map +2 -2
- package/dist/esm/decorators/subscribeSwcAppMessage.js +53 -0
- package/dist/esm/decorators/subscribeSwcAppMessage.js.map +7 -0
- package/dist/esm/decorators/{subscribeSwcAppRouteChangeWhileConnected.js → subscribeSwcAppRouteChange.js} +20 -16
- package/dist/esm/decorators/subscribeSwcAppRouteChange.js.map +7 -0
- package/dist/esm/decorators/swcAppRoute.js +130 -0
- package/dist/esm/decorators/swcAppRoute.js.map +7 -0
- package/dist/esm/elements/SwcApp.js +70 -0
- package/dist/esm/elements/SwcApp.js.map +2 -2
- package/dist/esm/elements/SwcAppMixin.js +264 -85
- package/dist/esm/elements/SwcAppMixin.js.map +3 -3
- package/dist/esm/types/index.js +5 -1
- package/dist/esm/types/index.js.map +2 -2
- package/dist/esm/utils/Utils.js +36 -15
- package/dist/esm/utils/Utils.js.map +2 -2
- package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +1893 -1000
- package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
- package/dist/types/SwcAppEngine.d.ts +1 -0
- package/dist/types/SwcAppEngine.d.ts.map +1 -1
- package/dist/types/decorators/addEventListener.d.ts +923 -960
- package/dist/types/decorators/addEventListener.d.ts.map +1 -1
- package/dist/types/decorators/applyAttribute.d.ts +26 -38
- package/dist/types/decorators/applyAttribute.d.ts.map +1 -1
- package/dist/types/decorators/applyClass.d.ts +1 -3
- package/dist/types/decorators/applyClass.d.ts.map +1 -1
- package/dist/types/decorators/applyNode.d.ts +15 -17
- package/dist/types/decorators/applyNode.d.ts.map +1 -1
- package/dist/types/decorators/applyProperty.d.ts +25 -45
- package/dist/types/decorators/applyProperty.d.ts.map +1 -1
- package/dist/types/decorators/applyStyle.d.ts +1 -3
- package/dist/types/decorators/applyStyle.d.ts.map +1 -1
- package/dist/types/decorators/around.d.ts +87 -0
- package/dist/types/decorators/around.d.ts.map +1 -0
- package/dist/types/decorators/changedAttribute.d.ts +21 -1
- package/dist/types/decorators/changedAttribute.d.ts.map +1 -1
- package/dist/types/decorators/elementDefine.d.ts.map +1 -1
- package/dist/types/decorators/emitCustomEvent.d.ts +0 -1
- package/dist/types/decorators/emitCustomEvent.d.ts.map +1 -1
- package/dist/types/decorators/eventMedia.d.ts +24 -1
- package/dist/types/decorators/eventMedia.d.ts.map +1 -1
- package/dist/types/decorators/fetch.d.ts +139 -0
- package/dist/types/decorators/fetch.d.ts.map +1 -0
- package/dist/types/decorators/index.d.ts +5 -2
- package/dist/types/decorators/index.d.ts.map +1 -1
- package/dist/types/decorators/intersectionObserver.d.ts +24 -11
- package/dist/types/decorators/intersectionObserver.d.ts.map +1 -1
- package/dist/types/decorators/lifecycles.d.ts +8 -7
- package/dist/types/decorators/lifecycles.d.ts.map +1 -1
- package/dist/types/decorators/mutationObserver.d.ts +26 -13
- package/dist/types/decorators/mutationObserver.d.ts.map +1 -1
- package/dist/types/decorators/parameter.d.ts +84 -6
- package/dist/types/decorators/parameter.d.ts.map +1 -1
- package/dist/types/decorators/publishSwcAppMessage.d.ts +0 -1
- package/dist/types/decorators/publishSwcAppMessage.d.ts.map +1 -1
- package/dist/types/decorators/query.d.ts +7 -7
- package/dist/types/decorators/query.d.ts.map +1 -1
- package/dist/types/decorators/requestAnimationFrame.d.ts +1 -1
- package/dist/types/decorators/requestAnimationFrame.d.ts.map +1 -1
- package/dist/types/decorators/resizeObserver.d.ts +26 -13
- package/dist/types/decorators/resizeObserver.d.ts.map +1 -1
- package/dist/types/decorators/setInterval.d.ts +10 -0
- package/dist/types/decorators/setInterval.d.ts.map +1 -1
- package/dist/types/decorators/setTimeout.d.ts +10 -0
- package/dist/types/decorators/setTimeout.d.ts.map +1 -1
- package/dist/types/decorators/state.d.ts.map +1 -1
- package/dist/types/decorators/subscribeSwcAppMessage.d.ts +44 -0
- package/dist/types/decorators/subscribeSwcAppMessage.d.ts.map +1 -0
- package/dist/types/decorators/subscribeSwcAppRouteChange.d.ts +60 -0
- package/dist/types/decorators/subscribeSwcAppRouteChange.d.ts.map +1 -0
- package/dist/types/decorators/swcAppRoute.d.ts +94 -0
- package/dist/types/decorators/swcAppRoute.d.ts.map +1 -0
- package/dist/types/elements/SwcApp.d.ts.map +1 -1
- package/dist/types/elements/SwcAppMixin.d.ts +423 -388
- package/dist/types/elements/SwcAppMixin.d.ts.map +1 -1
- package/dist/types/types/index.d.ts +43 -4
- package/dist/types/types/index.d.ts.map +1 -1
- package/dist/types/utils/Utils.d.ts +15 -5
- package/dist/types/utils/Utils.d.ts.map +1 -1
- package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +1893 -1000
- package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
- package/package.json +8 -6
- package/src/SwcAppEngine.ts +1 -0
- package/src/decorators/addEventListener.ts +102 -96
- package/src/decorators/applyAttribute.ts +76 -138
- package/src/decorators/applyClass.ts +8 -14
- package/src/decorators/applyNode.ts +33 -41
- package/src/decorators/applyProperty.ts +51 -99
- package/src/decorators/applySlot.ts +3 -3
- package/src/decorators/applyStyle.ts +8 -14
- package/src/decorators/around.ts +196 -0
- package/src/decorators/changedAttribute.ts +39 -15
- package/src/decorators/elementDefine.ts +270 -219
- package/src/decorators/emitCustomEvent.ts +17 -13
- package/src/decorators/eventMedia.ts +50 -9
- package/src/decorators/fetch.ts +454 -0
- package/src/decorators/index.ts +5 -2
- package/src/decorators/intersectionObserver.ts +38 -24
- package/src/decorators/lifecycles.ts +24 -22
- package/src/decorators/mutationObserver.ts +71 -52
- package/src/decorators/parameter.ts +145 -14
- package/src/decorators/publishSwcAppMessage.ts +3 -4
- package/src/decorators/query.ts +22 -14
- package/src/decorators/requestAnimationFrame.ts +10 -19
- package/src/decorators/resizeObserver.ts +50 -29
- package/src/decorators/setInterval.ts +37 -26
- package/src/decorators/setTimeout.ts +37 -26
- package/src/decorators/state.ts +19 -13
- package/src/decorators/subscribeSwcAppMessage.ts +106 -0
- package/src/decorators/subscribeSwcAppRouteChange.ts +131 -0
- package/src/decorators/swcAppRoute.ts +231 -0
- package/src/elements/SwcApp.ts +40 -10
- package/src/elements/SwcAppMixin.ts +340 -101
- package/src/types/index.ts +38 -5
- package/src/utils/Utils.ts +50 -18
- package/dist/cjs/decorators/subscribeSwcAppMessageWhileConnected.js.map +0 -7
- package/dist/cjs/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +0 -7
- package/dist/esm/decorators/subscribeSwcAppMessageWhileConnected.js +0 -54
- package/dist/esm/decorators/subscribeSwcAppMessageWhileConnected.js.map +0 -7
- package/dist/esm/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +0 -7
- package/dist/types/decorators/subscribeSwcAppMessageWhileConnected.d.ts +0 -23
- package/dist/types/decorators/subscribeSwcAppMessageWhileConnected.d.ts.map +0 -1
- package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts +0 -31
- package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts.map +0 -1
- package/src/decorators/subscribeSwcAppMessageWhileConnected.ts +0 -91
- package/src/decorators/subscribeSwcAppRouteChangeWhileConnected.ts +0 -107
|
@@ -1,10 +1,54 @@
|
|
|
1
|
-
import { SwcAppInterface, SwcAppMessage } from '../types';
|
|
2
|
-
import { APPLY_NODE_METADATA_KEY, findAllLifecycleMetadata,
|
|
1
|
+
import { SwcAppInterface, SwcAppMessage, SwcAppMessageObserveOptions, SwcElement } from '../types';
|
|
2
|
+
import { APPLY_NODE_METADATA_KEY, findAllLifecycleMetadata, getSubscribeSwcAppMessageMetadata, getSubscribeSwcAppRouteChangeMetadata, ON_CONNECTED_SWC_APP_METADATA_KEY, SUBSCRIBE_SWC_APP_ROUTE_CHANGE_METADATA_KEY, buildSwcParameterArgs } from '../decorators';
|
|
3
3
|
import { SwcAppEngine, SwcAttributeConfigType, SwcConfigType } from '../SwcAppEngine';
|
|
4
|
-
import { debounceTimeIntervalLock, FunctionUtils, Subscription } from '@dooboostore/core';
|
|
4
|
+
import { debounceTimeIntervalLock, FunctionUtils, Subscription, Subject, Observable } from '@dooboostore/core';
|
|
5
5
|
import {RouterEventType, ValidUtils} from '@dooboostore/core-web';
|
|
6
6
|
import { SwcUtils } from '../utils/Utils';
|
|
7
7
|
|
|
8
|
+
type RouteSubscriberState = { matched: boolean; signature: string; pathData?: { [k: string]: string }; pathDataAll?: { [k: string]: string[] } };
|
|
9
|
+
const routeStateOf = (instance: any): Map<string | symbol, RouteSubscriberState> => (instance.__swc_routeState ??= new Map());
|
|
10
|
+
|
|
11
|
+
/** 라우트 파라미터 데코레이터 값 (kind 이름 = 데코레이터 이름). 키를 받는 것은 (key) => 값 */
|
|
12
|
+
const routeParamValues = (sp: URLSearchParams, pathDataAll: { [k: string]: string[] }) => {
|
|
13
|
+
const queryAll: { [k: string]: string[] } = Object.fromEntries([...new Set(sp.keys())].map(k => [k, sp.getAll(k)]));
|
|
14
|
+
const pick = (all: { [k: string]: string[] }, at: 'first' | 'last') =>
|
|
15
|
+
Object.fromEntries(Object.entries(all).map(([k, v]) => [k, at === 'first' ? v[0] : v[v.length - 1]]));
|
|
16
|
+
const lastOf = (v: string[] | undefined) => (v && v.length ? v[v.length - 1] : undefined);
|
|
17
|
+
const firstQuery = (k: string) => sp.get(k);
|
|
18
|
+
const firstPath = (k: string) => pathDataAll[k]?.[0];
|
|
19
|
+
return {
|
|
20
|
+
swcAppRouteQueryParam: firstQuery,
|
|
21
|
+
swcAppRouteFirstQueryParam: firstQuery,
|
|
22
|
+
swcAppRouteLastQueryParam: (k: string) => lastOf(sp.getAll(k)) ?? null,
|
|
23
|
+
swcAppRouteQueryParams: (k: string) => sp.getAll(k),
|
|
24
|
+
swcAppRouteQueryParamObject: pick(queryAll, 'first'),
|
|
25
|
+
swcAppRouteFirstQueryParamObject: pick(queryAll, 'first'),
|
|
26
|
+
swcAppRouteLastQueryParamObject: pick(queryAll, 'last'),
|
|
27
|
+
swcAppRouteQueryParamsObject: queryAll,
|
|
28
|
+
swcAppRouteURLSearchParams: sp,
|
|
29
|
+
swcAppRoutePathVariable: firstPath,
|
|
30
|
+
swcAppRouteFirstPathVariable: firstPath,
|
|
31
|
+
swcAppRouteLastPathVariable: (k: string) => lastOf(pathDataAll[k]),
|
|
32
|
+
swcAppRoutePathVariables: (k: string) => pathDataAll[k] ?? [],
|
|
33
|
+
swcAppRoutePathVariableObject: pick(pathDataAll, 'first'),
|
|
34
|
+
swcAppRouteFirstPathVariableObject: pick(pathDataAll, 'first'),
|
|
35
|
+
swcAppRouteLastPathVariableObject: pick(pathDataAll, 'last'),
|
|
36
|
+
swcAppRoutePathVariablesObject: pathDataAll
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/** 구독자의 path(문자열/배열/함수/없음)를 route 에 매칭. 안 맞으면 null */
|
|
41
|
+
const matchRoute = (pathOption: any, instance: any, re: { path?: string }): { pathData: { [k: string]: string }; pathDataAll: { [k: string]: string[] } } | null => {
|
|
42
|
+
let pattern = typeof pathOption === 'function' ? pathOption(instance) : pathOption;
|
|
43
|
+
if (!pattern) return { pathData: {}, pathDataAll: {} };
|
|
44
|
+
const path = re.path || '/';
|
|
45
|
+
for (const p of Array.isArray(pattern) ? pattern : [pattern]) {
|
|
46
|
+
const pathData = SwcUtils.parsePathPattern(p, path);
|
|
47
|
+
if (pathData !== null) return { pathData, pathDataAll: SwcUtils.parsePathPatternAll(p, path) ?? {} };
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
};
|
|
51
|
+
|
|
8
52
|
export const isSSR = (i: HTMLElement) => {
|
|
9
53
|
return i.hasAttribute('swc-use-ssr');
|
|
10
54
|
};
|
|
@@ -12,6 +56,8 @@ export const setSSRAttribute = (i: HTMLElement) => {
|
|
|
12
56
|
i.setAttribute('swc-use-ssr', (i as any)._swcId ?? '');
|
|
13
57
|
};
|
|
14
58
|
export const removeSSRAttribute = (i: HTMLElement) => {
|
|
59
|
+
// 표식은 지워도 "서버가 그린 엘리먼트"였다는 건 기억한다 → 첫 라우트(같은 경로)를 다시 그리지 않게 (_invokeRouteChangeSubscribers)
|
|
60
|
+
if (isSSR(i)) (i as any).__swc_ssrRoute ??= true;
|
|
15
61
|
i.removeAttribute('swc-use-ssr');
|
|
16
62
|
};
|
|
17
63
|
|
|
@@ -25,11 +71,23 @@ export const removeSSRAttribute = (i: HTMLElement) => {
|
|
|
25
71
|
* }
|
|
26
72
|
*/
|
|
27
73
|
export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Base: T) {
|
|
28
|
-
|
|
74
|
+
abstract class SwcAppMixinClass extends Base implements SwcAppInterface {
|
|
29
75
|
__swc_engine = new SwcAppEngine(this as any);
|
|
76
|
+
_swcId!: string;
|
|
30
77
|
_swc_connected_instance = new Set<any>();
|
|
31
78
|
_routerSubscription?: Subscription;
|
|
32
79
|
_lastRouterEvent?: RouterEventType;
|
|
80
|
+
// replay 옵션으로 발행된 타입별 마지막 메시지 1개 보관
|
|
81
|
+
_replayedMessages = new Map<string, SwcAppMessage[]>();
|
|
82
|
+
// 코드 구독(observeMessage)용 live 스트림. 데코레이터 구독과 같은 publishMessage에서 흘러나옴.
|
|
83
|
+
_messageSubject = new Subject<SwcAppMessage>();
|
|
84
|
+
// ── 호스트 라이프사이클 훅 (abstract): 상속한 클래스가 반드시 구현. @inject 파라미터가 있으면 DI로 자동 해결됨 ──
|
|
85
|
+
// connectedCallback 시점 (DOM에 붙을 때마다). connect() 전이라 DI 컨테이너가 아직 없을 수 있음.
|
|
86
|
+
abstract onConnected(...args: any[]): void | Promise<void>;
|
|
87
|
+
// disconnectedCallback 시점 (DOM에서 떨어질 때마다). onConnected에서 만든 구독 등을 여기서 해제.
|
|
88
|
+
abstract onDisconnected(...args: any[]): void | Promise<void>;
|
|
89
|
+
// connect() 완료 후 (DI 컨테이너·라우터 준비됨). 서비스 호출은 여기서.
|
|
90
|
+
abstract onSwcAppConnected(...args: any[]): void | Promise<void>;
|
|
33
91
|
_routeChangeInvocations = 0;
|
|
34
92
|
_connectedInvocations = 0;
|
|
35
93
|
|
|
@@ -42,11 +100,8 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
|
|
|
42
100
|
* 부모가 자식들을 강제로 등록해주는 형태로 처리
|
|
43
101
|
*/
|
|
44
102
|
_connected_safari_and_standby = [];
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
super(...args);
|
|
48
|
-
console.log('[SWC-APP-MIXIN] constructor');
|
|
49
|
-
}
|
|
103
|
+
// connect() 전에 붙은 자식들이 기다리는 곳 — connect() 가 엔진을 띄운 뒤 깨운다
|
|
104
|
+
_appReadyWaiters: Array<() => void> = [];
|
|
50
105
|
|
|
51
106
|
get simpleApplication() {
|
|
52
107
|
return this.__swc_engine.simpleApplication;
|
|
@@ -60,7 +115,8 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
|
|
|
60
115
|
return this.__swc_engine?.router;
|
|
61
116
|
}
|
|
62
117
|
|
|
63
|
-
|
|
118
|
+
// phase: 'live'(기본, 전체 방송) | 'connected' | 'connectedDone' (replay, 해당 trigger만)
|
|
119
|
+
async _invokeRouteChangeSubscribers(instance: HTMLElement, re: RouterEventType, phase: 'live' | 'connected' | 'connectedDone' = 'live') {
|
|
64
120
|
if (instance.isConnected === false) return;
|
|
65
121
|
|
|
66
122
|
this._routeChangeInvocations++;
|
|
@@ -80,66 +136,78 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
|
|
|
80
136
|
);
|
|
81
137
|
|
|
82
138
|
try {
|
|
83
|
-
const routeChangeSubscribers =
|
|
139
|
+
const routeChangeSubscribers = getSubscribeSwcAppRouteChangeMetadata(instance);
|
|
84
140
|
|
|
85
141
|
if (routeChangeSubscribers.length > 0) {
|
|
86
|
-
// Subscribers are already sorted by order from
|
|
87
|
-
// Execute subscribers in order, stop if one returns a value
|
|
142
|
+
// Subscribers are already sorted by order from getSubscribeSwcAppRouteChangeMetadata
|
|
143
|
+
// Execute subscribers in order, stop if one returns a value.
|
|
144
|
+
// live는 전부 실행, replay는 해당 trigger 구독자만 실행.
|
|
145
|
+
// on(enter/update/leave) 판정을 위해 구독자별 직전 매칭 상태를 기억한다 (멈춘 뒤에도 상태는 계속 갱신).
|
|
146
|
+
const state = routeStateOf(instance);
|
|
147
|
+
// 서버가 이미 이 경로로 그려 보낸 엘리먼트는 같은 경로에 대해 다시 그리지 않는다 (@onConnected 렌더를 건너뛰는 것과 같은 규칙).
|
|
148
|
+
// 연결 때(connected)와 라우터 시작 때(live) 두 번 오므로 경로를 기억해 둘 다 건너뛰고, 다른 경로로 가면 잊는다.
|
|
149
|
+
// 매칭 상태는 아래에서 그대로 기록해 이후 enter/update/leave 판정은 정상으로 돈다.
|
|
150
|
+
const routeSig = JSON.stringify([re.path ?? '', re.search ?? '']);
|
|
151
|
+
if (ValidUtils.isBrowser() && isSSR(instance)) (instance as any).__swc_ssrRoute ??= true;
|
|
152
|
+
if ((instance as any).__swc_ssrRoute === true) (instance as any).__swc_ssrRoute = routeSig;
|
|
153
|
+
const ssrPainted = (instance as any).__swc_ssrRoute === routeSig;
|
|
154
|
+
if (!ssrPainted) delete (instance as any).__swc_ssrRoute;
|
|
155
|
+
let stopped = false;
|
|
88
156
|
for (const metadata of routeChangeSubscribers) {
|
|
157
|
+
if (phase !== 'live' && (metadata.options?.trigger ?? 'connected') !== phase) continue;
|
|
158
|
+
const on = metadata.options?.on ?? 'match';
|
|
159
|
+
if (on === 'beforeLeave') continue; // 라우터 가드로 처리 (_registerLeaveGuards)
|
|
89
160
|
const methodName = metadata.propertyKey;
|
|
90
|
-
let pathPattern = metadata.options?.path as any;
|
|
91
|
-
const filter = metadata.options?.filter;
|
|
92
161
|
const extractValue = (v: any) => {
|
|
93
|
-
const keyToUse = metadata.options?.valueKey ??
|
|
162
|
+
const keyToUse = metadata.options?.valueKey ?? SUBSCRIBE_SWC_APP_ROUTE_CHANGE_METADATA_KEY;
|
|
94
163
|
if (v && typeof v === 'object' && keyToUse in v) {
|
|
95
164
|
return v[keyToUse];
|
|
96
165
|
}
|
|
97
166
|
return v;
|
|
98
167
|
};
|
|
99
168
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
169
|
+
const match = matchRoute(metadata.options?.path, instance, re);
|
|
170
|
+
const prev = state.get(methodName);
|
|
171
|
+
// update 판정 기준: 경로 + query (path 변수는 경로에서 나오므로 포함됨. 패턴 없는 bare 구독도 경로 변경을 감지)
|
|
172
|
+
const signature = match ? JSON.stringify([re.path ?? '', re.search ?? '']) : '';
|
|
173
|
+
state.set(methodName, match ? { matched: true, signature, pathData: match.pathData, pathDataAll: match.pathDataAll } : { matched: false, signature: '' });
|
|
103
174
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
for (const pattern of pathPattern) {
|
|
112
|
-
const data = SwcUtils.parsePathPattern(pattern, re.path || '/');
|
|
113
|
-
if (data !== null) {
|
|
114
|
-
pathMatched = true;
|
|
115
|
-
pathData = data;
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
} else {
|
|
120
|
-
pathData = SwcUtils.parsePathPattern(pathPattern, re.path || '/');
|
|
121
|
-
pathMatched = pathData !== null;
|
|
122
|
-
}
|
|
175
|
+
const shouldRun =
|
|
176
|
+
on === 'match' ? !!match
|
|
177
|
+
: on === 'enter' ? !!match && !prev?.matched
|
|
178
|
+
: on === 'update' ? !!match && !!prev?.matched && prev.signature !== signature
|
|
179
|
+
: on === 'leave' ? !match && !!prev?.matched
|
|
180
|
+
: false;
|
|
181
|
+
if (stopped || ssrPainted || !shouldRun || !instance[methodName]) continue;
|
|
123
182
|
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
183
|
+
// leave 는 지금 경로가 안 맞으므로 직전 매칭의 pathData 로 준다
|
|
184
|
+
const pathData = match ? match.pathData : prev?.pathData ?? {};
|
|
185
|
+
const pathDataAll = match ? match.pathDataAll : prev?.pathDataAll ?? {};
|
|
186
|
+
|
|
187
|
+
const filter = metadata.options?.filter;
|
|
188
|
+
const instanceHelperHostSet = SwcUtils.getHelperAndHostSet(instance, this.config.window);
|
|
189
|
+
const filterPassed = !filter || (await filter(this.router!, { helper: instanceHelperHostSet, currentThis: instance }));
|
|
190
|
+
if (!filterPassed) continue;
|
|
191
|
+
|
|
192
|
+
const before = metadata.options?.before;
|
|
193
|
+
const finaly = metadata.options?.finally;
|
|
194
|
+
// before를 먼저 돌려 리턴값을 @routeChangeBeforeReturn 으로 주입할 수 있게 캡처.
|
|
195
|
+
const beforeReturn = before ? await before(this.router!, { helper: instanceHelperHostSet, currentThis: instance }) : undefined;
|
|
196
|
+
const routeEventValue = { ...re, pathData };
|
|
197
|
+
const args = this._buildRouteArgs(instance, methodName, routeEventValue, pathDataAll, beforeReturn);
|
|
198
|
+
let rawResult: any, error: any;
|
|
199
|
+
try {
|
|
200
|
+
rawResult = await instance[methodName](...args);
|
|
201
|
+
} catch (e) {
|
|
202
|
+
error = e;
|
|
203
|
+
} finally {
|
|
204
|
+
if (finaly) await finaly(this.router!, { helper: instanceHelperHostSet, currentThis: instance }, { args, result: rawResult, error });
|
|
205
|
+
}
|
|
206
|
+
if (error) throw error;
|
|
207
|
+
const result = extractValue(rawResult);
|
|
208
|
+
// If handler returns a value, stop propagation to next handlers
|
|
209
|
+
if (result !== undefined && result !== null) {
|
|
210
|
+
stopped = true;
|
|
143
211
|
}
|
|
144
212
|
}
|
|
145
213
|
}
|
|
@@ -155,11 +223,70 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
|
|
|
155
223
|
}
|
|
156
224
|
}
|
|
157
225
|
|
|
226
|
+
/** 라우트 핸들러 인자: @swcAppRouterEvent / host 계열 / @routeChangeBeforeReturn / 라우트 파라미터(query·path 변수) */
|
|
227
|
+
_buildRouteArgs(instance: any, methodName: string | symbol, routeEventValue: any, pathDataAll: { [k: string]: string[] }, beforeReturn: any) {
|
|
228
|
+
const instanceHelperHostSet = SwcUtils.getHelperAndHostSet(instance, this.config.window);
|
|
229
|
+
const sp: URLSearchParams = routeEventValue.searchParams ?? new URLSearchParams(routeEventValue.search ?? '');
|
|
230
|
+
return buildSwcParameterArgs(
|
|
231
|
+
instance,
|
|
232
|
+
methodName,
|
|
233
|
+
{
|
|
234
|
+
swcAppRouterEvent: routeEventValue,
|
|
235
|
+
hostSet: instanceHelperHostSet,
|
|
236
|
+
helperHostSet: instanceHelperHostSet,
|
|
237
|
+
helperSet: SwcUtils.getHelperSet(this.config.window),
|
|
238
|
+
routeChangeBeforeReturn: beforeReturn,
|
|
239
|
+
...routeParamValues(sp, pathDataAll)
|
|
240
|
+
},
|
|
241
|
+
[routeEventValue]
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* on: 'beforeLeave' 구독을 라우터 가드로 등록. 지금 경로는 맞고 다음 경로는 안 맞을 때 핸들러를 부르고,
|
|
247
|
+
* false 를 리턴하면 이동을 취소한다. 요소가 떨어지면 _disconnected 에서 해제.
|
|
248
|
+
*/
|
|
249
|
+
_registerLeaveGuards(instance: any) {
|
|
250
|
+
if (!this.router?.addLeaveGuard) return;
|
|
251
|
+
instance.__swc_leaveGuardOffs?.forEach((off: () => void) => off());
|
|
252
|
+
instance.__swc_leaveGuardOffs = getSubscribeSwcAppRouteChangeMetadata(instance)
|
|
253
|
+
.filter(m => m.options?.on === 'beforeLeave')
|
|
254
|
+
.map(metadata =>
|
|
255
|
+
this.router!.addLeaveGuard(async (to, from) => {
|
|
256
|
+
if (instance.isConnected === false || !instance[metadata.propertyKey]) return true;
|
|
257
|
+
const fromMatch = matchRoute(metadata.options?.path, instance, from);
|
|
258
|
+
if (!fromMatch || matchRoute(metadata.options?.path, instance, to)) return true; // 이 라우트를 떠나는 경우만
|
|
259
|
+
const filter = metadata.options?.filter;
|
|
260
|
+
const helper = SwcUtils.getHelperAndHostSet(instance, this.config.window);
|
|
261
|
+
if (filter && !(await filter(this.router!, { helper, currentThis: instance }))) return true;
|
|
262
|
+
const beforeReturn = metadata.options?.before ? await metadata.options.before(this.router!, { helper, currentThis: instance }) : undefined;
|
|
263
|
+
const routeEventValue = { ...from, pathData: fromMatch.pathData, to };
|
|
264
|
+
const args = this._buildRouteArgs(instance, metadata.propertyKey, routeEventValue, fromMatch.pathDataAll, beforeReturn);
|
|
265
|
+
let result: any, error: any;
|
|
266
|
+
try {
|
|
267
|
+
result = await instance[metadata.propertyKey](...args);
|
|
268
|
+
} catch (e) {
|
|
269
|
+
error = e;
|
|
270
|
+
} finally {
|
|
271
|
+
if (metadata.options?.finally) await metadata.options.finally(this.router!, { helper, currentThis: instance }, { args, result, error });
|
|
272
|
+
}
|
|
273
|
+
if (error) throw error;
|
|
274
|
+
return result !== false;
|
|
275
|
+
})
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
158
279
|
async _connected(instance: HTMLElement, option?: { noIncrements: boolean }) {
|
|
159
280
|
if (instance) {
|
|
160
281
|
if (!this.simpleApplication && !this.config) {
|
|
161
|
-
|
|
162
|
-
|
|
282
|
+
// 자기 자신은 connect() 가 나중에 재생한다
|
|
283
|
+
if (instance === (this as any)) {
|
|
284
|
+
this._connected_safari_and_standby.push(instance);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
// 앱이 아직 connect 전: 준비될 때까지 기다린다. 안 기다리면 이미 정의된 자식이 DI·라우터 없이
|
|
288
|
+
// 라이프사이클(@inject 등)을 먼저 돌려 undefined 를 받는다 (elementDefine 이 이 await 뒤에 렌더한다).
|
|
289
|
+
await new Promise<void>(resolve => this._appReadyWaiters.push(resolve));
|
|
163
290
|
}
|
|
164
291
|
|
|
165
292
|
this.config.onConnectedChildBefore?.(instance);
|
|
@@ -167,10 +294,14 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
|
|
|
167
294
|
this._connectedInvocations++;
|
|
168
295
|
}
|
|
169
296
|
this._swc_connected_instance.add(instance);
|
|
297
|
+
// await 사이에 요소가 떨어지거나 다시 붙으면(elementDefine 의 차례 번호가 바뀌면) 나머지(가드 등록·재생)는 건너뛴다
|
|
298
|
+
const gen = (instance as any).__swc_connectGen;
|
|
299
|
+
const stale = () => (instance as any).__swc_connectGen !== gen;
|
|
170
300
|
|
|
171
301
|
const connectedSwcAppCallBacks = findAllLifecycleMetadata(instance, ON_CONNECTED_SWC_APP_METADATA_KEY);
|
|
172
302
|
for (let connectedSwcAppCallBack of connectedSwcAppCallBacks) {
|
|
173
303
|
await (instance as any)?._invokeLifecycleMethod?.(connectedSwcAppCallBack.propertyKey);
|
|
304
|
+
if (stale()) return this;
|
|
174
305
|
}
|
|
175
306
|
|
|
176
307
|
if (this._childrenConnectedDoneInterval) {
|
|
@@ -190,15 +321,20 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
|
|
|
190
321
|
if (this.simpleApplication) {
|
|
191
322
|
try {
|
|
192
323
|
if (this.router && this._lastRouterEvent) {
|
|
193
|
-
await this._invokeRouteChangeSubscribers(instance, this._lastRouterEvent
|
|
324
|
+
await this._invokeRouteChangeSubscribers(instance, this._lastRouterEvent!, 'connected');
|
|
194
325
|
}
|
|
195
326
|
} catch (e) {
|
|
196
327
|
throw e;
|
|
197
328
|
}
|
|
198
329
|
}
|
|
330
|
+
if (stale()) return this;
|
|
331
|
+
|
|
332
|
+
if (this.simpleApplication) this._registerLeaveGuards(instance);
|
|
333
|
+
|
|
334
|
+
// replay 버퍼가 있으면 신규 인스턴스에만 재생 (fire-and-forget, 연결 블로킹 안 함)
|
|
335
|
+
this._replayMessagesTo(instance);
|
|
199
336
|
|
|
200
337
|
const isBrowser = ValidUtils.isBrowser();
|
|
201
|
-
console.log('------>', isBrowser, this.config?.ssr)
|
|
202
338
|
if (!isBrowser && this.config?.ssr) {
|
|
203
339
|
setSSRAttribute(instance);
|
|
204
340
|
} else {
|
|
@@ -210,9 +346,20 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
|
|
|
210
346
|
return this;
|
|
211
347
|
}
|
|
212
348
|
|
|
213
|
-
async _connectedDone(instance: any) {
|
|
349
|
+
async _connectedDone(instance: any, option?: { aborted?: boolean }) {
|
|
214
350
|
try {
|
|
215
|
-
//
|
|
351
|
+
if (option?.aborted) return; // 중간에 멈춘 연결: 재생 없이 연결 수만 맞춘다
|
|
352
|
+
// trigger 'connectedDone' 구독에만 버퍼 재생 (render 완료 후라 DOM 타겟 안전)
|
|
353
|
+
this._replayMessagesTo(instance, 'connectedDone');
|
|
354
|
+
if (this.simpleApplication) {
|
|
355
|
+
try {
|
|
356
|
+
if (this.router && this._lastRouterEvent) {
|
|
357
|
+
await this._invokeRouteChangeSubscribers(instance, this._lastRouterEvent!, 'connectedDone');
|
|
358
|
+
}
|
|
359
|
+
} catch (e) {
|
|
360
|
+
throw e;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
216
363
|
} finally {
|
|
217
364
|
this._connectedInvocations = Math.max(0, this._connectedInvocations - 1);
|
|
218
365
|
}
|
|
@@ -221,6 +368,10 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
|
|
|
221
368
|
_disconnected(instance: any) {
|
|
222
369
|
if (instance) {
|
|
223
370
|
this._swc_connected_instance.delete(instance);
|
|
371
|
+
// beforeLeave 가드 해제 + on 판정 상태 초기화 (다시 붙으면 enter 부터)
|
|
372
|
+
instance.__swc_leaveGuardOffs?.forEach((off: () => void) => off());
|
|
373
|
+
instance.__swc_leaveGuardOffs = undefined;
|
|
374
|
+
instance.__swc_routeState = undefined;
|
|
224
375
|
this.config?.onDisconnectedChildAfter?.(instance);
|
|
225
376
|
}
|
|
226
377
|
}
|
|
@@ -233,7 +384,6 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
|
|
|
233
384
|
}
|
|
234
385
|
|
|
235
386
|
async connect(config?: SwcAttributeConfigType) {
|
|
236
|
-
console.log('[SWC-MIXIN] connect');
|
|
237
387
|
const swcConfig = {
|
|
238
388
|
routeType: 'element',
|
|
239
389
|
window: config?.window,
|
|
@@ -241,6 +391,7 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
|
|
|
241
391
|
} as SwcConfigType;
|
|
242
392
|
|
|
243
393
|
await this.__swc_engine.connect(swcConfig);
|
|
394
|
+
this._appReadyWaiters.splice(0).forEach(resolve => resolve());
|
|
244
395
|
|
|
245
396
|
// Safari is 속성 처리
|
|
246
397
|
this._connected_safari_and_standby.forEach((instance: any) => {
|
|
@@ -249,19 +400,15 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
|
|
|
249
400
|
|
|
250
401
|
// 라우터 변경 구독
|
|
251
402
|
if (!this._routerSubscription && this.router) {
|
|
252
|
-
const config = this.__swc_engine.config;
|
|
253
|
-
|
|
254
403
|
this._routerSubscription = this.router.observable.subscribe(async (route: RouterEventType) => {
|
|
255
|
-
console.log('[SWC-MIXIN] Router event received:', route.triggerPoint);
|
|
256
404
|
if (route.triggerPoint === 'end') {
|
|
257
405
|
await this._handleRouteChange(route);
|
|
258
406
|
}
|
|
259
407
|
});
|
|
260
|
-
|
|
261
|
-
if (config?.path) {
|
|
262
|
-
console.log('[DEBUG] Navigating to initial path:', config.path);
|
|
263
|
-
}
|
|
264
408
|
}
|
|
409
|
+
|
|
410
|
+
// onSwcAppConnected 훅 (@inject 파라미터 있으면 DI 해결 — _invokeLifecycleMethod 경로)
|
|
411
|
+
await this._invokeHook('onSwcAppConnected');
|
|
265
412
|
}
|
|
266
413
|
|
|
267
414
|
connectedCallback() {
|
|
@@ -276,7 +423,7 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
|
|
|
276
423
|
const userConfig = FunctionUtils.executeReturn({
|
|
277
424
|
script: configStr,
|
|
278
425
|
context: this,
|
|
279
|
-
args: SwcUtils.getHelperAndHostSet(
|
|
426
|
+
args: SwcUtils.getHelperAndHostSet(this, win)
|
|
280
427
|
});
|
|
281
428
|
if (userConfig instanceof Promise) {
|
|
282
429
|
userConfig
|
|
@@ -297,6 +444,7 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
|
|
|
297
444
|
this._connected(this);
|
|
298
445
|
this.__swc_engine?.config?.onConnected?.(this);
|
|
299
446
|
this._connectedDone(this);
|
|
447
|
+
void this._invokeHook('onConnected').catch(e => console.error('[SWC-MIXIN] onConnected error:', e));
|
|
300
448
|
}
|
|
301
449
|
|
|
302
450
|
disconnectedCallback() {
|
|
@@ -310,6 +458,7 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
|
|
|
310
458
|
this.__swc_engine.disconnect();
|
|
311
459
|
this.__swc_engine?.config?.onDisconnected?.(this);
|
|
312
460
|
this._disconnected(this);
|
|
461
|
+
void this._invokeHook('onDisconnected').catch(e => console.error('[SWC-MIXIN] onDisconnected error:', e));
|
|
313
462
|
}
|
|
314
463
|
|
|
315
464
|
async routing(path: string) {
|
|
@@ -328,45 +477,135 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
|
|
|
328
477
|
this.router?.reload();
|
|
329
478
|
}
|
|
330
479
|
|
|
331
|
-
publishMessage(message: SwcAppMessage): void {
|
|
480
|
+
publishMessage(message: SwcAppMessage): void { // 버퍼 먼저 (핸들러가 동기 재발행해도 순서 보장)
|
|
481
|
+
// 전 타입 과거 메시지 보관 (늦게 연결될 인스턴스에 재생). 개수는 앱 설정 1곳.
|
|
482
|
+
if (message.type) {
|
|
483
|
+
const buf = [...(this._replayedMessages.get(message.type) ?? []), message];
|
|
484
|
+
this._replayedMessages.set(message.type, buf.slice(-Math.max(1, this.config?.messageReplayBufferSize ?? 10)));
|
|
485
|
+
}
|
|
332
486
|
this._swc_connected_instance.forEach((instance: any) => {
|
|
333
487
|
this._invokeMessageSubscribers(instance, message);
|
|
334
488
|
});
|
|
489
|
+
this._messageSubject.next(message);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
connectedElements(): Array<HTMLElement & SwcElement> {
|
|
493
|
+
return Array.from(this._swc_connected_instance);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* 데코레이터 없이 코드로 메시지를 구독한다 (RxJS 스타일). 서비스·동적 구독 등 데코레이터를 못 붙이는 곳용.
|
|
498
|
+
* host.observeMessage<Me>(AUTH_CHANGED).subscribe(msg => ...) → Subscription (unsubscribe 필수)
|
|
499
|
+
* type 생략 시 전 타입 live. subject 옵션은 @subscribeSwcAppMessage 와 같은 규칙(type 지정 시에만 재생):
|
|
500
|
+
* 'behavior' = 버퍼 마지막 1개 먼저 / 'replay' = 버퍼 전체 시간순 먼저 / 미지정·'subject' = live만.
|
|
501
|
+
* 호출 형태:
|
|
502
|
+
* observeMessage() 전 타입 live
|
|
503
|
+
* observeMessage('auth') auth live
|
|
504
|
+
* observeMessage('auth', { subject: 'behavior' }) auth 마지막 1개 + live
|
|
505
|
+
* observeMessage({ type: 'auth', subject: 'replay' }) 옵션 객체 하나로
|
|
506
|
+
*/
|
|
507
|
+
observeMessage<T = any>(): Observable<SwcAppMessage<T>>;
|
|
508
|
+
observeMessage<T = any>(type: string): Observable<SwcAppMessage<T>>;
|
|
509
|
+
observeMessage<T = any>(type: string, options: Omit<SwcAppMessageObserveOptions, 'type'>): Observable<SwcAppMessage<T>>;
|
|
510
|
+
observeMessage<T = any>(options: SwcAppMessageObserveOptions): Observable<SwcAppMessage<T>>;
|
|
511
|
+
observeMessage<T = any>(typeOrOptions?: string | SwcAppMessageObserveOptions, options?: Omit<SwcAppMessageObserveOptions, 'type'>): Observable<SwcAppMessage<T>> {
|
|
512
|
+
const { type, subject } = typeof typeOrOptions === 'string' ? { ...options, type: typeOrOptions } : { ...typeOrOptions };
|
|
513
|
+
return new Observable<SwcAppMessage<T>>(subscriber => {
|
|
514
|
+
if (type && (subject === 'behavior' || subject === 'replay')) {
|
|
515
|
+
const buf = this._replayedMessages.get(type) ?? [];
|
|
516
|
+
(subject === 'behavior' ? buf.slice(-1) : buf).forEach(m => subscriber.next(m as SwcAppMessage<T>));
|
|
517
|
+
}
|
|
518
|
+
const sub = this._messageSubject.subscribe(m => {
|
|
519
|
+
if (!type || m.type === type) subscriber.next(m as SwcAppMessage<T>);
|
|
520
|
+
});
|
|
521
|
+
return () => sub.unsubscribe();
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// hook 실행 + @inject 파라미터 DI 해결 (host 본체 포함.
|
|
526
|
+
// _invokeLifecycleMethod는 host/hostSet 상황 분기가 달라 호스트 본체에서 주입이 안 됨)
|
|
527
|
+
async _invokeHook(methodName: string | symbol) {
|
|
528
|
+
const fn = (this as any)[methodName];
|
|
529
|
+
if (typeof fn !== 'function') return;
|
|
530
|
+
const manager = (this as any).simpleApplication?.simstanceManager;
|
|
531
|
+
if (!manager) {
|
|
532
|
+
return fn.call(this);
|
|
533
|
+
}
|
|
534
|
+
return manager.executeBindParameterSimPromise({ target: this, targetKey: methodName });
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// 신규 연결 인스턴스 1곳에만 버퍼 재생. 구독자별 subject 선언 기준:
|
|
538
|
+
// - 'behavior': 마지막 1개 / 'replay': 버퍼 전체 시간순 / 미지정·'subject': live만 (재생 안 함)
|
|
539
|
+
// - trigger 'connectedDone'인 구독은 _connectedDone(render 완료 후)에서 재생. phase로 구분.
|
|
540
|
+
// 이미 연결된 인스턴스는 publishMessage 때 live로 받으므로 여기 오지 않음.
|
|
541
|
+
_replayMessagesTo(instance: any, phase: 'connected' | 'connectedDone' = 'connected') {
|
|
542
|
+
const subs = getSubscribeSwcAppMessageMetadata(instance);
|
|
543
|
+
if (!subs || !Array.isArray(subs)) return;
|
|
544
|
+
const seen = new Set<string>();
|
|
545
|
+
subs.forEach(meta => {
|
|
546
|
+
const type = meta.messageType as string | undefined;
|
|
547
|
+
if (!type || seen.has(type)) return;
|
|
548
|
+
seen.add(type);
|
|
549
|
+
if ((meta.trigger ?? 'connected') !== phase) return;
|
|
550
|
+
const buf = this._replayedMessages.get(type) ?? [];
|
|
551
|
+
if (meta.subject === 'behavior') {
|
|
552
|
+
const last = buf[buf.length - 1];
|
|
553
|
+
if (last) this._invokeMessageSubscribers(instance, last);
|
|
554
|
+
} else if (meta.subject === 'replay') {
|
|
555
|
+
buf.forEach(msg => this._invokeMessageSubscribers(instance, msg));
|
|
556
|
+
}
|
|
557
|
+
});
|
|
335
558
|
}
|
|
336
559
|
|
|
337
560
|
_invokeMessageSubscribers(instance: any, message: SwcAppMessage) {
|
|
338
|
-
const messageSubscribers =
|
|
561
|
+
const messageSubscribers = getSubscribeSwcAppMessageMetadata(instance);
|
|
339
562
|
if (messageSubscribers && Array.isArray(messageSubscribers)) {
|
|
563
|
+
// 메시지 구독은 fire-and-forget — async filter/before/finally를 위해 구독자마다 비동기 러너로 돌리되
|
|
564
|
+
// 호출측(publishMessage)은 논블로킹 유지. 미처리 rejection은 로깅만.
|
|
340
565
|
messageSubscribers.forEach(metadata => {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
let typeMatched = false;
|
|
346
|
-
if (!messageType) {
|
|
347
|
-
typeMatched = true;
|
|
348
|
-
} else if (message.type === messageType) {
|
|
349
|
-
typeMatched = true;
|
|
350
|
-
}
|
|
566
|
+
void this._runMessageSubscriber(instance, message, metadata).catch(e => console.error('[SWC] subscribeSwcAppMessage error:', e));
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
}
|
|
351
570
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
571
|
+
async _runMessageSubscriber(instance: any, message: SwcAppMessage, metadata: any) {
|
|
572
|
+
const methodName = metadata.propertyKey;
|
|
573
|
+
const messageType = metadata.messageType;
|
|
574
|
+
const { filter, before, finally: finaly } = metadata;
|
|
575
|
+
|
|
576
|
+
const typeMatched = !messageType || message.type === messageType;
|
|
577
|
+
if (!typeMatched) return;
|
|
578
|
+
if (filter && !(await filter(message, instance))) return;
|
|
579
|
+
if (typeof instance[methodName] !== 'function') return;
|
|
580
|
+
|
|
581
|
+
const instanceHelperHostSet = SwcUtils.getHelperAndHostSet(instance, this.config.window);
|
|
582
|
+
const instanceHelperSet = SwcUtils.getHelperSet(this.config.window);
|
|
583
|
+
// before를 먼저 돌려 리턴값을 @appMessageBeforeReturn 으로 주입할 수 있게 캡처.
|
|
584
|
+
const beforeReturn = before ? await before(message, instance) : undefined;
|
|
585
|
+
const args = buildSwcParameterArgs(
|
|
586
|
+
instance,
|
|
587
|
+
methodName,
|
|
588
|
+
{
|
|
589
|
+
appMessage: message,
|
|
590
|
+
hostSet: instanceHelperHostSet,
|
|
591
|
+
helperHostSet: instanceHelperHostSet,
|
|
592
|
+
helperSet: instanceHelperSet,
|
|
593
|
+
appMessageBeforeReturn: beforeReturn
|
|
594
|
+
},
|
|
595
|
+
[message]
|
|
596
|
+
);
|
|
356
597
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
helperSet: instanceHelperSet
|
|
365
|
-
}, [message]);
|
|
366
|
-
instance[methodName](...args);
|
|
367
|
-
}
|
|
368
|
-
});
|
|
598
|
+
let result: any, error: any;
|
|
599
|
+
try {
|
|
600
|
+
result = await instance[methodName](...args);
|
|
601
|
+
} catch (e) {
|
|
602
|
+
error = e;
|
|
603
|
+
} finally {
|
|
604
|
+
if (finaly) await finaly(message, instance, { args, result, error });
|
|
369
605
|
}
|
|
606
|
+
if (error) throw error;
|
|
370
607
|
}
|
|
371
|
-
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
return SwcAppMixinClass;
|
|
372
611
|
}
|