@dooboostore/simple-boot-http-server-ssr 1.0.17 → 1.0.18

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dooboostore/simple-boot-http-server-ssr",
3
- "version": "1.0.17",
3
+ "version": "1.0.18",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -67,16 +67,7 @@
67
67
  "url": "https://github.com/dooboostore-develop/packages"
68
68
  },
69
69
  "peerDependencies": {
70
- "reflect-metadata": "^0.2.2",
71
- "@dooboostore/core": "1.0.32",
72
- "@dooboostore/dom-render": "1.0.114",
73
- "@dooboostore/dom-parser": "1.0.7",
74
- "@dooboostore/core-node": "1.0.13",
75
- "@dooboostore/simple-boot-front": "1.0.132",
76
- "@dooboostore/core-web": "1.0.13",
77
- "@dooboostore/simple-boot": "1.0.57",
78
- "@dooboostore/simple-web-component": "1.0.47",
79
- "@dooboostore/simple-boot-http-server": "1.0.26"
70
+ "reflect-metadata": "^0.2.2"
80
71
  },
81
72
  "devDependencies": {
82
73
  "@babel/core": "^7.24.0",
@@ -100,7 +91,16 @@
100
91
  "dependencies": {
101
92
  "jsdom": "^26.0.0",
102
93
  "linkedom": "0.18.12",
103
- "playwright": "^1.59.1"
94
+ "playwright": "^1.59.1",
95
+ "@dooboostore/core-web": "1.0.14",
96
+ "@dooboostore/core-node": "1.0.14",
97
+ "@dooboostore/core": "1.0.33",
98
+ "@dooboostore/dom-render": "1.0.115",
99
+ "@dooboostore/dom-parser": "1.0.8",
100
+ "@dooboostore/simple-boot": "1.0.58",
101
+ "@dooboostore/simple-boot-front": "1.0.133",
102
+ "@dooboostore/simple-boot-http-server": "1.0.28",
103
+ "@dooboostore/simple-web-component": "1.0.51"
104
104
  },
105
105
  "scripts": {
106
106
  "clean": "rm -rf dist",
@@ -4,7 +4,11 @@ import {Filter} from '@dooboostore/simple-boot-http-server/filters/Filter';
4
4
  import {Mimes} from '@dooboostore/simple-boot-http-server/codes/Mimes';
5
5
  import {HttpStatus} from '@dooboostore/simple-boot-http-server/codes/HttpStatus';
6
6
  import {SimpleBootHttpServer} from '@dooboostore/simple-boot-http-server/SimpleBootHttpServer';
7
+ import {injectRequestResponse} from '@dooboostore/simple-boot-http-server/proxy/RequestResponseInjectProxy';
8
+ import {SimConfig} from '@dooboostore/simple-boot';
7
9
  import {DomParserInitializer} from '../initializers/DomParserInitializer';
10
+ import type { SwcAppInterface } from '@dooboostore/simple-web-component';
11
+ import { findAllPropertyMetadata } from '@dooboostore/simple-web-component';
8
12
 
9
13
  export type SWCSSRDomParserConfig = {
10
14
  frontDistPath: string;
@@ -12,9 +16,18 @@ export type SWCSSRDomParserConfig = {
12
16
  welcomUrl?: string;
13
17
  ssrExcludeFilter?: (rr: RequestResponse) => boolean;
14
18
  /**
15
- * Function to register components for each request.
19
+ * SSR 에 노출할 백엔드 서비스 목록(각 항목은 symbol 을 가진다, 예: pairServices).
20
+ * 프레임워크가 요청마다 각 서비스를 꺼내 이 요청의 rr 을 주입한 뒤 Map 으로 묶어
21
+ * registerComponents 의 3번째 인자(sim)로 넘긴다. → 앱은 그 Map 을 bootfactory 에 그대로 전달만 하면 되고
22
+ * 직접 Proxy 로 감싸거나 rr 을 넣을 필요가 없다(쿠키 등 요청 컨텍스트 자동 전달).
16
23
  */
17
- registerComponents?: (window: any) => Promise<void> | void;
24
+ intentServices?: SimConfig[];
25
+ /**
26
+ * 요청마다 컴포넌트를 등록한다. 앱 bootfactory를 그대로 넘기면 된다.
27
+ * @param sim intentServices 를 rr 주입해 묶은 Map<symbol, service>. bootfactory 에 그대로 넘기면 된다.
28
+ * @returns 초기화된 SwcApp (하이드레이션 데이터 박제 등에 사용)
29
+ */
30
+ registerComponents?: (window: any, rr: RequestResponse, sim: Map<symbol, any>) => Promise<SwcAppInterface | void> | SwcAppInterface | void;
18
31
  };
19
32
 
20
33
  /**
@@ -23,12 +36,15 @@ export type SWCSSRDomParserConfig = {
23
36
  */
24
37
  export class SSRSimpleWebComponentDomParserFilter implements Filter {
25
38
  private welcomUrl = 'http://localhost';
39
+ private app?: SimpleBootHttpServer;
26
40
 
27
41
  constructor(public config: SWCSSRDomParserConfig) {
28
42
  this.welcomUrl = config.welcomUrl || this.welcomUrl;
29
43
  }
30
44
 
31
- async onInit(app: SimpleBootHttpServer) {}
45
+ async onInit(app: SimpleBootHttpServer) {
46
+ this.app = app;
47
+ }
32
48
 
33
49
  async onDestroy() {}
34
50
 
@@ -63,8 +79,68 @@ export class SSRSimpleWebComponentDomParserFilter implements Filter {
63
79
  try {
64
80
  // 2. Register Components if provided
65
81
  // We use SwcApplication inside the callback or directly here.
82
+ let app: SwcAppInterface | void;
66
83
  if (this.config.registerComponents) {
67
- await this.config.registerComponents(window);
84
+ // intentServices 를 rr 주입해 Map 으로 묶어 넘긴다 (앱은 bootfactory 에 그대로 전달만).
85
+ const sim = new Map<symbol, any>();
86
+ for (const service of (this.config.intentServices ?? []).filter(Boolean)) {
87
+ if (!service.symbol) continue;
88
+ for (const sym of (Array.isArray(service.symbol) ? service.symbol : [service.symbol])) {
89
+ const raw = this.app?.sim<any>(sym as any);
90
+ if (raw) sim.set(sym, injectRequestResponse(raw, rr));
91
+ }
92
+ }
93
+ app = await this.config.registerComponents(window, rr, sim);
94
+ }
95
+
96
+ // [하이드레이션] swcApp 본체 + 연결 엘리먼트의 @property 값을 script로 박제.
97
+ // 클라에서 번들보다 먼저 실행 → querySelector로 찾아 .prop = 값 세팅 (네이티브
98
+ // 업그레이드는 own property 유지라 upgrade 후에도 값 살아있음).
99
+ if (app) {
100
+ const items: Array<{ sel: string; prop: string; value: unknown }> = [];
101
+ const targets: any[] = [app, ...(app.connectedElements?.() ?? []).filter((el: any) => el !== app)];
102
+ for (const el of targets) {
103
+ const swcId = (el as any)._swcId;
104
+ if (!swcId) continue;
105
+ const sel = `[swc-use-ssr="${swcId}"]`;
106
+ for (const m of findAllPropertyMetadata(el)) {
107
+ let value: unknown;
108
+ try {
109
+ value = el[m.propertyKey];
110
+ } catch {
111
+ continue;
112
+ }
113
+ if (value === undefined || typeof value === 'function') continue;
114
+ try {
115
+ JSON.stringify(value);
116
+ } catch {
117
+ continue;
118
+ }
119
+ items.push({ sel, prop: String(m.propertyKey), value });
120
+ }
121
+ }
122
+ if (items.length > 0) {
123
+ // 업그레이드 때 노드가 갈아엎어지면 JS 프로퍼티는 날아감. attribute는 복사되니
124
+ // data-hyd-* 로 박고, 같은 노드면 프로퍼티도 바로 세팅.
125
+ const script = window.document.createElement('script');
126
+ // <script> 내용은 이스케이프 없이 직렬화된다 → 값에 '</script>'가 있으면 HTML 파서가 거기서 태그를 닫아 XSS.
127
+ // JSON.stringify는 '<'를 이스케이프하지 않으므로 '\u003c'로 바꾼다 (JS에선 같은 문자열).
128
+ // U+2028/2029는 구형 JS 엔진에서 문자열 리터럴 안의 줄바꿈으로 해석돼 문법 오류가 나므로 함께 이스케이프.
129
+ const hydrationJson = JSON.stringify(items)
130
+ .replace(/</g, '\\u003c')
131
+ .replace(/\u2028/g, '\\u2028')
132
+ .replace(/\u2029/g, '\\u2029');
133
+ script.textContent =
134
+ ` window.__swc_hydration=${hydrationJson};` +
135
+ `for(const h of window.__swc_hydration){
136
+ const el=document.querySelector(h.sel);
137
+ if(el){
138
+ el[h.prop]=h.value;
139
+ }
140
+ }`;
141
+ window.document.body.appendChild(script);
142
+ console.log(`[ssr-hydration] ${items.length} props embedded`);
143
+ }
68
144
  }
69
145
 
70
146
  // window.document.querySelector('.sidebar-space').innerHTML = '씨발놈아.';
@@ -19,7 +19,7 @@ export type SWCSSRConfig = {
19
19
  * Playwright evaluates real JS files loaded from your index.html,
20
20
  * so component registration happens automatically in the browser context.
21
21
  */
22
- registerComponents?: (window: any) => Promise<void> | void;
22
+ registerComponents?: (window: any, rr?: RequestResponse) => Promise<void> | void;
23
23
  playwright?: {
24
24
  waitUntil?: 'load' | 'domcontentloaded' | 'networkidle' | 'commit';
25
25
  waitForSelector?: string;