@bstage-sdk/bridge 0.9.1 → 0.10.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.
- package/README.md +57 -0
- package/dist/{chunk-6LV7HEN4.js → chunk-JH3A3Z2A.js} +3 -1
- package/dist/{chunk-KQMJY4ZH.js → chunk-N3JID7KQ.js} +82 -2
- package/dist/frame.cjs +82 -2
- package/dist/frame.d.cts +10 -6
- package/dist/frame.d.ts +10 -6
- package/dist/frame.js +1 -1
- package/dist/host.cjs +124 -7
- package/dist/host.d.cts +24 -6
- package/dist/host.d.ts +24 -6
- package/dist/host.js +44 -7
- package/dist/index.cjs +85 -2
- package/dist/index.d.cts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +4 -2
- package/dist/{messages-9N0GFq5b.d.cts → messages-CRGnyf-6.d.cts} +6 -1
- package/dist/{messages-9N0GFq5b.d.ts → messages-CRGnyf-6.d.ts} +6 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -79,6 +79,62 @@ host.dispose()
|
|
|
79
79
|
모두 맞는 메시지만 처리한다. 토큰은 프레임으로 가지 않는다 — 인증은 `fetchImpl`이 호스트 쪽에서 붙이고,
|
|
80
80
|
프레임에는 상태·statusText·content-type·본문 문자열만 돌아간다.
|
|
81
81
|
|
|
82
|
+
### 세션 수명과 프레임 자기 내비게이션
|
|
83
|
+
|
|
84
|
+
템플릿은 자기 프레임을 다른 URL로 옮길 수 있다(`location = 'https://attacker/#' + session`). sandbox 플래그는
|
|
85
|
+
내비게이션 뒤에도 남으므로 새 문서도 origin `"null"`이고 `contentWindow`도 같다 — `ev.source`·origin 검사만으로는
|
|
86
|
+
옛 문서와 새 문서를 가를 수 없고, 세션은 srcdoc 본문에 있어 템플릿이 읽어 URL로 넘길 수 있다. 호스트가 iframe의
|
|
87
|
+
두 번째 `load`에서 `dispose()`해도 새 문서의 스크립트는 `load`보다 먼저 돈다. SDK는 이 틈을 다음으로 닫는다
|
|
88
|
+
(호스트 코드 변경 없음).
|
|
89
|
+
|
|
90
|
+
- **세션당 문서 하나(프레임 토큰)**: 프레임 런타임이 부트스트랩 때 문서마다 128비트 토큰을 만들어 모든 봉투 최상위
|
|
91
|
+
`frame`에 싣는다. 호스트는 세션이 맞는 첫 메시지의 토큰에 세션을 묶고, 이후 **토큰이 없거나 다른** 메시지가 오면
|
|
92
|
+
그 메시지를 처리하지 않고 즉시 `dispose()`한다. 같은 토큰의 `ready` 재전송에는 계속 답하지만, 다른 문서의 두 번째
|
|
93
|
+
핸드셰이크는 성립하지 않는다. 세션을 URL로 받은 새 문서는 토큰을 모르므로 첫 메시지에서 세션이 끊긴다.
|
|
94
|
+
- **토큰은 CSPRNG로만 만든다**: `crypto.getRandomValues`가 없으면 `Math.random`으로 대신하지 않고 브리지를 열지 않는다(`frame-unload` 외에는 아무것도 보내지 않는다).
|
|
95
|
+
- **핸드셰이크 전에는 이벤트를 보내지 않는다**: `host.send`는 토큰이 묶이기 전에는 프레임에 보내지 않고 init에만 반영한다. init에 접히지 않는 `visibility`는 마지막 값을 묶인 직후(init 뒤) 한 번 보낸다. 공개되는 봉투 id 접두사와 따로 뽑는다.
|
|
96
|
+
- **토큰은 템플릿 코드에 노출하지 않는다**: 런타임 클로저에만 있고, 봉투는 객체 리터럴로 만들며(Object.prototype
|
|
97
|
+
setter를 부르지 않는다) 부트스트랩 때 `bind`해 둔 `parent.postMessage`로만 보낸다. 호스트 → 프레임 메시지
|
|
98
|
+
(init·응답·이벤트)에는 실리지 않는다. 그래서 템플릿이 `Object.assign`·`Function.prototype.*`·`Object.prototype`
|
|
99
|
+
setter를 가로채도 보이는 것은 세션뿐이다(테스트로 고정). 세션은 템플릿이 읽을 수 있다 — 같은 문서이고 srcdoc 본문에
|
|
100
|
+
있기 때문이다. 세션은 "이 iframe의 메시지"라는 표식일 뿐 비밀로 취급하지 않는다.
|
|
101
|
+
- **`frame-unload`**: 부트스트랩이 `pagehide`(capture, 템플릿보다 먼저 등록)에서 `evt:frame-unload`를 동기로 보낸다.
|
|
102
|
+
**전송을 가장 먼저** 하고, 전송 경로는 객체 리터럴과 부트스트랩 때 잡아 둔 참조(`bind`한 `postMessage`,
|
|
103
|
+
`PageTransitionEvent.prototype.persisted` 원본 접근자를 `Function.prototype.call.bind`로 묶은 것)만 쓴다.
|
|
104
|
+
`clearInterval`·`clearTimeout`도 부트스트랩 때 잡고, 타이머·대기 요청 정리는 전송 뒤 각각 try로 감싼다. 그래서
|
|
105
|
+
템플릿이 **리스너가 살아 있는 동안** 전역·프로토타입(`clearInterval`·`clearTimeout`·`Map.prototype.forEach`·`Error`·
|
|
106
|
+
`Function.prototype.call`·`persisted` 접근자)을 바꾸거나 던지게 해도 전송은 막히지 않는다(테스트로 고정).
|
|
107
|
+
`persisted` 읽기가 실패하면 bfcache 진입이 아닌 것으로 보고 보낸다(실패는 끊는 쪽으로).
|
|
108
|
+
보장하지 **않는** 것: 템플릿이 `document.open()`으로 창의 이벤트 리스너를 모두 지운 뒤 내비게이션하면 `pagehide`
|
|
109
|
+
처리기 자체가 돌지 않는다 — 이때는 프레임 토큰과 호스트의 두 번째 `load` dispose에 기댄다.
|
|
110
|
+
호스트는 받는 즉시 `dispose()`한다 — 이후 메시지는 무시되고, dispose 뒤 끝난 요청의 응답도 프레임에 보내지 않는다.
|
|
111
|
+
프레임 쪽 대기 요청은 `UNSUPPORTED`로 바로 거절된다. bfcache 진입(`persisted: true`)에는 보내지 않는다(복원되면 같은 문서가 계속 쓴다).
|
|
112
|
+
- **`onDispose(reason)`**(선택): 호스트가 dispose될 때 한 번 불린다. `'frame-unload'`·`'frame-mismatch'`는 호스트가
|
|
113
|
+
스스로 끊은 경우, `'manual'`은 `dispose()` 호출이다. 임베더는 이 콜백에서 iframe을 바로 걷어 낼 수 있다(콜백 예외는 삼킨다).
|
|
114
|
+
`beforeunload`·`unload`는 쓰지 않는다 — `beforeunload`는 호스트 페이지 자체의 이동에도 프레임에 오므로 이동이
|
|
115
|
+
취소되거나 bfcache에서 복원되면 멀쩡한 템플릿이 끊기고, 하위 프레임의 `unload` 리스너는 호스트 페이지의 bfcache를
|
|
116
|
+
막고 폐기 중인 API다.
|
|
117
|
+
|
|
118
|
+
**남는 위험(정직하게)**
|
|
119
|
+
|
|
120
|
+
- 막는 것은 "새 문서가 유효한 교환을 하는 것"이다. 새 문서가 **메시지를 보내지 않고 기다리면**, 호스트가 알아채기
|
|
121
|
+
전까지(`frame-unload` 처리 또는 두 번째 `load`) `host.send`로 보내는 이벤트(`locale`·`theme`·`member`의
|
|
122
|
+
`signedIn`·`nickname`·`slot.context`·`visibility`)를 받을 수 있다. Chromium은 다른 프로세스로 가는 하위 프레임
|
|
123
|
+
내비게이션에서 옛 문서의 `pagehide`를 새 문서 커밋과 병렬로 돌리므로 이 틈은 0이 아니다. 이벤트·init에 민감 정보를
|
|
124
|
+
싣지 않는다는 원칙이 여기서도 경계다.
|
|
125
|
+
- 토큰 비공개는 JS 수준 보장이다. 엔진 버그나 이 SDK가 놓친 관찰 경로로 원래 문서의 템플릿이 토큰을 얻어 URL로 넘기면,
|
|
126
|
+
새 문서는 `frame-unload`가 처리되기 전까지 유효한 요청을 보낼 수 있다. 이 경우의 마지막 방어선은 `frame-unload`와
|
|
127
|
+
호스트의 두 번째 `load` dispose다.
|
|
128
|
+
- **진행 중인 요청의 응답**: 원래 문서가 보낸 요청의 응답이 내비게이션 뒤·dispose 전에 끝나면, 호스트는 그 응답을
|
|
129
|
+
`iframe.contentWindow`(= 새 문서)로 보낸다. 새 문서는 응답 봉투를 읽을 수 있다. dispose 뒤에 끝난 응답은 버리지만
|
|
130
|
+
호스트 쪽 fetch 자체를 중단하지는 않는다(dispose 시 진행 중 fetch abort는 후속 과제).
|
|
131
|
+
- 세션을 아는 쪽(= 그 프레임의 템플릿)은 잘못된 토큰을 보내 자기 세션을 끊을 수 있다. 자기 자신에 대한 서비스 거부일 뿐이다.
|
|
132
|
+
- 호스트는 srcdoc마다 새 세션(`createSession()`)을 만들고 세션 하나에 `createBridgeHost`를 하나만 둔다. 같은 세션으로
|
|
133
|
+
호스트를 다시 만들면 묶임이 새로 시작된다.
|
|
134
|
+
- **같은 srcdoc의 재로드·재삽입에는 새 호스트가 필요하다**: iframe을 DOM에서 옮기거나 다시 넣거나 `srcdoc`를 다시 설정하면
|
|
135
|
+
새 문서(새 프레임 토큰)가 뜨므로, 옛 호스트는 `frame-unload`나 `frame-mismatch`로 끊기고 그 문서는 브리지를 쓰지 못한다.
|
|
136
|
+
이때는 새 세션으로 `createSrcdoc`·`createBridgeHost`를 다시 만든다(`onDispose`로 감지할 수 있다).
|
|
137
|
+
|
|
82
138
|
### fetch 정책
|
|
83
139
|
|
|
84
140
|
`new URL(url, hostOrigin)`으로 정규화한 뒤 목적지를 네 갈래로 나눈다.
|
|
@@ -159,6 +215,7 @@ external에 인증을 붙이는 fetch를 주입하지 않는다. 외부 갈래
|
|
|
159
215
|
|
|
160
216
|
오류 코드: `FORBIDDEN_DESTINATION`·`FORBIDDEN_METHOD`·`BODY_TOO_LARGE`·`RATE_LIMITED`·`BAD_REQUEST`·`TIMEOUT`·`NETWORK_ERROR`·`UNSUPPORTED`.
|
|
161
217
|
프레임 요청은 15초 안에 응답이 없으면 `TIMEOUT`. 핸드셰이크(`ready`)는 init을 받을 때까지 재전송한다(처음 10초는 250ms, 이후 2초 간격).
|
|
218
|
+
프레임 → 호스트 봉투에는 최상위 `frame`(프레임 토큰)이 붙고, 문서가 내려갈 때 `evt:frame-unload`(`FRAME_UNLOAD_EVENT`)가 간다(위 "세션 수명").
|
|
162
219
|
|
|
163
220
|
## 프레임 문서
|
|
164
221
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
bridgeRuntime,
|
|
3
3
|
createBridgeFetch
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-N3JID7KQ.js";
|
|
5
5
|
|
|
6
6
|
// src/protocol/envelope.ts
|
|
7
7
|
var BRIDGE_VERSION = 1;
|
|
@@ -65,6 +65,7 @@ var REQUEST_TYPES = [
|
|
|
65
65
|
var HOST_EVENT_TYPES = ["slot.context", "locale", "member", "theme", "visibility"];
|
|
66
66
|
var READY_EVENT = "ready";
|
|
67
67
|
var INIT_RESPONSE = "init";
|
|
68
|
+
var FRAME_UNLOAD_EVENT = "frame-unload";
|
|
68
69
|
var BRIDGE_SURFACES = ["page", "slot"];
|
|
69
70
|
function isBridgeSurface(value) {
|
|
70
71
|
return BRIDGE_SURFACES.includes(value);
|
|
@@ -551,6 +552,7 @@ export {
|
|
|
551
552
|
HOST_EVENT_TYPES,
|
|
552
553
|
READY_EVENT,
|
|
553
554
|
INIT_RESPONSE,
|
|
555
|
+
FRAME_UNLOAD_EVENT,
|
|
554
556
|
BRIDGE_SURFACES,
|
|
555
557
|
isBridgeSurface,
|
|
556
558
|
IFRAME_ATTRIBUTES,
|
|
@@ -103,6 +103,20 @@ function bridgeRuntime(g, makeFetch, config) {
|
|
|
103
103
|
const session = config.session;
|
|
104
104
|
const hostOrigin = config.hostOrigin;
|
|
105
105
|
const parent = g.parent;
|
|
106
|
+
const sendToHost = parent ? parent.postMessage.bind(parent) : void 0;
|
|
107
|
+
const stopInterval = clearInterval;
|
|
108
|
+
const stopTimeout = clearTimeout;
|
|
109
|
+
let readPersisted;
|
|
110
|
+
const ownDescriptor = Object.getOwnPropertyDescriptor;
|
|
111
|
+
const hasOwn = Function.prototype.call.bind(Object.prototype.hasOwnProperty);
|
|
112
|
+
try {
|
|
113
|
+
const pte = g.PageTransitionEvent;
|
|
114
|
+
const desc = pte ? Object.getOwnPropertyDescriptor(pte.prototype, "persisted") : void 0;
|
|
115
|
+
if (desc && typeof desc.get === "function")
|
|
116
|
+
readPersisted = Function.prototype.call.bind(desc.get);
|
|
117
|
+
} catch {
|
|
118
|
+
readPersisted = void 0;
|
|
119
|
+
}
|
|
106
120
|
const pending = /* @__PURE__ */ new Map();
|
|
107
121
|
const listeners = /* @__PURE__ */ new Map();
|
|
108
122
|
let init;
|
|
@@ -119,6 +133,17 @@ function bridgeRuntime(g, makeFetch, config) {
|
|
|
119
133
|
return out;
|
|
120
134
|
}
|
|
121
135
|
const idPrefix = randomPrefix();
|
|
136
|
+
function secureToken() {
|
|
137
|
+
if (!g.crypto || typeof g.crypto.getRandomValues !== "function") return "";
|
|
138
|
+
const bytes = new Uint8Array(16);
|
|
139
|
+
g.crypto.getRandomValues(bytes);
|
|
140
|
+
let out = "";
|
|
141
|
+
for (let i = 0; i < bytes.length; i += 1) out += (bytes[i] + 256).toString(16).slice(1);
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
const frameToken = secureToken();
|
|
145
|
+
let closed = frameToken === "";
|
|
146
|
+
let unloaded = false;
|
|
122
147
|
function nextId() {
|
|
123
148
|
seq += 1;
|
|
124
149
|
return idPrefix + "-" + seq;
|
|
@@ -130,9 +155,12 @@ function bridgeRuntime(g, makeFetch, config) {
|
|
|
130
155
|
return err;
|
|
131
156
|
}
|
|
132
157
|
function post(kind, type, id, payload) {
|
|
133
|
-
if (!
|
|
158
|
+
if (!sendToHost || closed) return;
|
|
134
159
|
const body = Object.assign({}, payload, { session });
|
|
135
|
-
|
|
160
|
+
sendToHost(
|
|
161
|
+
{ v: 1, ns: NS, id, kind, type, payload: body, frame: frameToken },
|
|
162
|
+
hostOrigin
|
|
163
|
+
);
|
|
136
164
|
}
|
|
137
165
|
function isEnvelope(m) {
|
|
138
166
|
if (typeof m !== "object" || m === null) return false;
|
|
@@ -205,6 +233,54 @@ function bridgeRuntime(g, makeFetch, config) {
|
|
|
205
233
|
}
|
|
206
234
|
}
|
|
207
235
|
g.addEventListener("message", onMessage);
|
|
236
|
+
function isPersisted(ev) {
|
|
237
|
+
try {
|
|
238
|
+
if (readPersisted) return readPersisted(ev) === true;
|
|
239
|
+
if (typeof ev !== "object" || ev === null) return false;
|
|
240
|
+
const desc = ownDescriptor(ev, "persisted");
|
|
241
|
+
return !!desc && hasOwn(desc, "value") && desc.value === true;
|
|
242
|
+
} catch {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function onPageHide(ev) {
|
|
247
|
+
if (unloaded || isPersisted(ev)) return;
|
|
248
|
+
unloaded = true;
|
|
249
|
+
closed = true;
|
|
250
|
+
if (sendToHost) {
|
|
251
|
+
try {
|
|
252
|
+
sendToHost(
|
|
253
|
+
{
|
|
254
|
+
v: 1,
|
|
255
|
+
ns: NS,
|
|
256
|
+
id: idPrefix + "-unload",
|
|
257
|
+
kind: "evt",
|
|
258
|
+
type: "frame-unload",
|
|
259
|
+
payload: { session },
|
|
260
|
+
frame: frameToken
|
|
261
|
+
},
|
|
262
|
+
hostOrigin
|
|
263
|
+
);
|
|
264
|
+
} catch {
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
stopInterval(readyTimer);
|
|
269
|
+
} catch {
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
pending.forEach(function(entry) {
|
|
273
|
+
try {
|
|
274
|
+
stopTimeout(entry.timer);
|
|
275
|
+
entry.reject(codeError("UNSUPPORTED", "bridge closed"));
|
|
276
|
+
} catch {
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
pending.clear();
|
|
280
|
+
} catch {
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
g.addEventListener("pagehide", onPageHide, true);
|
|
208
284
|
let readyAttempts = 0;
|
|
209
285
|
function sendReady() {
|
|
210
286
|
readyAttempts += 1;
|
|
@@ -218,6 +294,10 @@ function bridgeRuntime(g, makeFetch, config) {
|
|
|
218
294
|
sendReady();
|
|
219
295
|
function request(type, payload) {
|
|
220
296
|
return new Promise(function(resolve, reject) {
|
|
297
|
+
if (closed) {
|
|
298
|
+
reject(codeError("UNSUPPORTED", "bridge closed"));
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
221
301
|
const id = nextId();
|
|
222
302
|
const timer = setTimeout(function() {
|
|
223
303
|
pending.delete(id);
|
package/dist/frame.cjs
CHANGED
|
@@ -130,6 +130,20 @@ function bridgeRuntime(g, makeFetch, config) {
|
|
|
130
130
|
const session = config.session;
|
|
131
131
|
const hostOrigin = config.hostOrigin;
|
|
132
132
|
const parent = g.parent;
|
|
133
|
+
const sendToHost = parent ? parent.postMessage.bind(parent) : void 0;
|
|
134
|
+
const stopInterval = clearInterval;
|
|
135
|
+
const stopTimeout = clearTimeout;
|
|
136
|
+
let readPersisted;
|
|
137
|
+
const ownDescriptor = Object.getOwnPropertyDescriptor;
|
|
138
|
+
const hasOwn = Function.prototype.call.bind(Object.prototype.hasOwnProperty);
|
|
139
|
+
try {
|
|
140
|
+
const pte = g.PageTransitionEvent;
|
|
141
|
+
const desc = pte ? Object.getOwnPropertyDescriptor(pte.prototype, "persisted") : void 0;
|
|
142
|
+
if (desc && typeof desc.get === "function")
|
|
143
|
+
readPersisted = Function.prototype.call.bind(desc.get);
|
|
144
|
+
} catch {
|
|
145
|
+
readPersisted = void 0;
|
|
146
|
+
}
|
|
133
147
|
const pending = /* @__PURE__ */ new Map();
|
|
134
148
|
const listeners = /* @__PURE__ */ new Map();
|
|
135
149
|
let init;
|
|
@@ -146,6 +160,17 @@ function bridgeRuntime(g, makeFetch, config) {
|
|
|
146
160
|
return out;
|
|
147
161
|
}
|
|
148
162
|
const idPrefix = randomPrefix();
|
|
163
|
+
function secureToken() {
|
|
164
|
+
if (!g.crypto || typeof g.crypto.getRandomValues !== "function") return "";
|
|
165
|
+
const bytes = new Uint8Array(16);
|
|
166
|
+
g.crypto.getRandomValues(bytes);
|
|
167
|
+
let out = "";
|
|
168
|
+
for (let i = 0; i < bytes.length; i += 1) out += (bytes[i] + 256).toString(16).slice(1);
|
|
169
|
+
return out;
|
|
170
|
+
}
|
|
171
|
+
const frameToken = secureToken();
|
|
172
|
+
let closed = frameToken === "";
|
|
173
|
+
let unloaded = false;
|
|
149
174
|
function nextId() {
|
|
150
175
|
seq += 1;
|
|
151
176
|
return idPrefix + "-" + seq;
|
|
@@ -157,9 +182,12 @@ function bridgeRuntime(g, makeFetch, config) {
|
|
|
157
182
|
return err;
|
|
158
183
|
}
|
|
159
184
|
function post(kind, type, id, payload) {
|
|
160
|
-
if (!
|
|
185
|
+
if (!sendToHost || closed) return;
|
|
161
186
|
const body = Object.assign({}, payload, { session });
|
|
162
|
-
|
|
187
|
+
sendToHost(
|
|
188
|
+
{ v: 1, ns: NS, id, kind, type, payload: body, frame: frameToken },
|
|
189
|
+
hostOrigin
|
|
190
|
+
);
|
|
163
191
|
}
|
|
164
192
|
function isEnvelope(m) {
|
|
165
193
|
if (typeof m !== "object" || m === null) return false;
|
|
@@ -232,6 +260,54 @@ function bridgeRuntime(g, makeFetch, config) {
|
|
|
232
260
|
}
|
|
233
261
|
}
|
|
234
262
|
g.addEventListener("message", onMessage);
|
|
263
|
+
function isPersisted(ev) {
|
|
264
|
+
try {
|
|
265
|
+
if (readPersisted) return readPersisted(ev) === true;
|
|
266
|
+
if (typeof ev !== "object" || ev === null) return false;
|
|
267
|
+
const desc = ownDescriptor(ev, "persisted");
|
|
268
|
+
return !!desc && hasOwn(desc, "value") && desc.value === true;
|
|
269
|
+
} catch {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function onPageHide(ev) {
|
|
274
|
+
if (unloaded || isPersisted(ev)) return;
|
|
275
|
+
unloaded = true;
|
|
276
|
+
closed = true;
|
|
277
|
+
if (sendToHost) {
|
|
278
|
+
try {
|
|
279
|
+
sendToHost(
|
|
280
|
+
{
|
|
281
|
+
v: 1,
|
|
282
|
+
ns: NS,
|
|
283
|
+
id: idPrefix + "-unload",
|
|
284
|
+
kind: "evt",
|
|
285
|
+
type: "frame-unload",
|
|
286
|
+
payload: { session },
|
|
287
|
+
frame: frameToken
|
|
288
|
+
},
|
|
289
|
+
hostOrigin
|
|
290
|
+
);
|
|
291
|
+
} catch {
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
stopInterval(readyTimer);
|
|
296
|
+
} catch {
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
pending.forEach(function(entry) {
|
|
300
|
+
try {
|
|
301
|
+
stopTimeout(entry.timer);
|
|
302
|
+
entry.reject(codeError("UNSUPPORTED", "bridge closed"));
|
|
303
|
+
} catch {
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
pending.clear();
|
|
307
|
+
} catch {
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
g.addEventListener("pagehide", onPageHide, true);
|
|
235
311
|
let readyAttempts = 0;
|
|
236
312
|
function sendReady() {
|
|
237
313
|
readyAttempts += 1;
|
|
@@ -245,6 +321,10 @@ function bridgeRuntime(g, makeFetch, config) {
|
|
|
245
321
|
sendReady();
|
|
246
322
|
function request(type, payload) {
|
|
247
323
|
return new Promise(function(resolve, reject) {
|
|
324
|
+
if (closed) {
|
|
325
|
+
reject(codeError("UNSUPPORTED", "bridge closed"));
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
248
328
|
const id = nextId();
|
|
249
329
|
const timer = setTimeout(function() {
|
|
250
330
|
pending.delete(id);
|
package/dist/frame.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { g as BridgeInit, j as BridgeRequestType, i as BridgeRequestMap, e as BridgeEventMap } from './messages-
|
|
1
|
+
import { g as BridgeInit, j as BridgeRequestType, i as BridgeRequestMap, e as BridgeEventMap } from './messages-CRGnyf-6.cjs';
|
|
2
2
|
|
|
3
3
|
/** 프레임 런타임 설정 — srcdoc 부트스트랩이 상수로 넘긴다. */
|
|
4
4
|
interface BridgeClientConfig {
|
|
@@ -28,14 +28,18 @@ interface RuntimeGlobal {
|
|
|
28
28
|
parent: {
|
|
29
29
|
postMessage(message: unknown, targetOrigin: string): void;
|
|
30
30
|
} | null;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
/**
|
|
32
|
+
* `message`(호스트 응답·이벤트)와 `pagehide`(frame-unload 전송, capture)에 쓴다.
|
|
33
|
+
* 처리기 인자 모양은 type마다 다르다 — message는 `{ source, origin, data }`, pagehide는 `{ persisted }`.
|
|
34
|
+
*/
|
|
35
|
+
addEventListener(type: 'message' | 'pagehide', fn: (ev: never) => void, capture?: boolean): void;
|
|
36
36
|
crypto?: {
|
|
37
37
|
getRandomValues<T extends ArrayBufferView | null>(array: T): T;
|
|
38
38
|
};
|
|
39
|
+
/** pagehide 이벤트 생성자 — `persisted` 원본 접근자를 부트스트랩 때 잡는 데 쓴다(없으면 이벤트 속성을 읽는다). */
|
|
40
|
+
PageTransitionEvent?: {
|
|
41
|
+
readonly prototype: object;
|
|
42
|
+
};
|
|
39
43
|
__bstage_bridge__?: BridgeClient;
|
|
40
44
|
}
|
|
41
45
|
/** `createBridgeFetch`가 요구하는 클라이언트 부분. */
|
package/dist/frame.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { g as BridgeInit, j as BridgeRequestType, i as BridgeRequestMap, e as BridgeEventMap } from './messages-
|
|
1
|
+
import { g as BridgeInit, j as BridgeRequestType, i as BridgeRequestMap, e as BridgeEventMap } from './messages-CRGnyf-6.js';
|
|
2
2
|
|
|
3
3
|
/** 프레임 런타임 설정 — srcdoc 부트스트랩이 상수로 넘긴다. */
|
|
4
4
|
interface BridgeClientConfig {
|
|
@@ -28,14 +28,18 @@ interface RuntimeGlobal {
|
|
|
28
28
|
parent: {
|
|
29
29
|
postMessage(message: unknown, targetOrigin: string): void;
|
|
30
30
|
} | null;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
/**
|
|
32
|
+
* `message`(호스트 응답·이벤트)와 `pagehide`(frame-unload 전송, capture)에 쓴다.
|
|
33
|
+
* 처리기 인자 모양은 type마다 다르다 — message는 `{ source, origin, data }`, pagehide는 `{ persisted }`.
|
|
34
|
+
*/
|
|
35
|
+
addEventListener(type: 'message' | 'pagehide', fn: (ev: never) => void, capture?: boolean): void;
|
|
36
36
|
crypto?: {
|
|
37
37
|
getRandomValues<T extends ArrayBufferView | null>(array: T): T;
|
|
38
38
|
};
|
|
39
|
+
/** pagehide 이벤트 생성자 — `persisted` 원본 접근자를 부트스트랩 때 잡는 데 쓴다(없으면 이벤트 속성을 읽는다). */
|
|
40
|
+
PageTransitionEvent?: {
|
|
41
|
+
readonly prototype: object;
|
|
42
|
+
};
|
|
39
43
|
__bstage_bridge__?: BridgeClient;
|
|
40
44
|
}
|
|
41
45
|
/** `createBridgeFetch`가 요구하는 클라이언트 부분. */
|
package/dist/frame.js
CHANGED
package/dist/host.cjs
CHANGED
|
@@ -107,6 +107,7 @@ var REQUEST_TYPES = [
|
|
|
107
107
|
var HOST_EVENT_TYPES = ["slot.context", "locale", "member", "theme", "visibility"];
|
|
108
108
|
var READY_EVENT = "ready";
|
|
109
109
|
var INIT_RESPONSE = "init";
|
|
110
|
+
var FRAME_UNLOAD_EVENT = "frame-unload";
|
|
110
111
|
var BRIDGE_SURFACES = ["page", "slot"];
|
|
111
112
|
function isBridgeSurface(value) {
|
|
112
113
|
return BRIDGE_SURFACES.includes(value);
|
|
@@ -844,6 +845,9 @@ function assertOptions(options) {
|
|
|
844
845
|
"[bstage-bridge] externalFetchImpl\uC5D0 partnerFetchImpl(\uD32C \uD1A0\uD070 fetch)\uC744 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4"
|
|
845
846
|
);
|
|
846
847
|
}
|
|
848
|
+
if (options.onDispose !== void 0 && typeof options.onDispose !== "function") {
|
|
849
|
+
throw new TypeError("[bstage-bridge] onDispose\uB294 \uD568\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4");
|
|
850
|
+
}
|
|
847
851
|
if (!options.iframe || !options.init) {
|
|
848
852
|
throw new TypeError("[bstage-bridge] iframe\xB7init\uC774 \uD544\uC694\uD569\uB2C8\uB2E4");
|
|
849
853
|
}
|
|
@@ -866,6 +870,8 @@ function createBridgeHost(options) {
|
|
|
866
870
|
let current = buildInit(options.init, hostOrigin, partnerOrigins, policy);
|
|
867
871
|
let inFlightFetches = 0;
|
|
868
872
|
let disposed = false;
|
|
873
|
+
let boundFrame;
|
|
874
|
+
let pendingVisibility;
|
|
869
875
|
let resizeHandle;
|
|
870
876
|
let pendingHeight;
|
|
871
877
|
const raf = typeof win.requestAnimationFrame === "function" ? (cb) => win.requestAnimationFrame(cb) : (cb) => setTimeout(cb, 16);
|
|
@@ -929,13 +935,44 @@ function createBridgeHost(options) {
|
|
|
929
935
|
const env = ev.data;
|
|
930
936
|
if (!isBridgeEnvelope(env)) return;
|
|
931
937
|
if (!hasSession(env.payload, session)) return;
|
|
938
|
+
const token = env.frame;
|
|
939
|
+
if (!isValidSession(token) || boundFrame !== void 0 && token !== boundFrame) {
|
|
940
|
+
dispose("frame-mismatch");
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
boundFrame = token;
|
|
944
|
+
if (env.kind === "evt" && env.type === FRAME_UNLOAD_EVENT) {
|
|
945
|
+
dispose("frame-unload");
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
932
948
|
if (env.kind === "evt" && env.type === READY_EVENT) {
|
|
933
949
|
if (!readyLimiter.take()) return;
|
|
934
950
|
post(frame, createEnvelope("res", INIT_RESPONSE, env.id, { ok: true, data: current }));
|
|
951
|
+
flushPendingVisibility(frame);
|
|
935
952
|
return;
|
|
936
953
|
}
|
|
954
|
+
flushPendingVisibility(frame);
|
|
937
955
|
if (env.kind === "req") void handleRequest(frame, env);
|
|
938
956
|
}
|
|
957
|
+
function flushPendingVisibility(frame) {
|
|
958
|
+
if (pendingVisibility === void 0) return;
|
|
959
|
+
const payload = pendingVisibility;
|
|
960
|
+
pendingVisibility = void 0;
|
|
961
|
+
post(frame, createEnvelope("evt", "visibility", createSession(), payload));
|
|
962
|
+
}
|
|
963
|
+
function dispose(reason) {
|
|
964
|
+
if (disposed) return;
|
|
965
|
+
disposed = true;
|
|
966
|
+
win.removeEventListener("message", onMessage);
|
|
967
|
+
if (resizeHandle !== void 0) cancelRaf(resizeHandle);
|
|
968
|
+
resizeHandle = void 0;
|
|
969
|
+
if (options.onDispose) {
|
|
970
|
+
try {
|
|
971
|
+
options.onDispose(reason);
|
|
972
|
+
} catch {
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
939
976
|
win.addEventListener("message", onMessage);
|
|
940
977
|
return {
|
|
941
978
|
send(type, payload) {
|
|
@@ -946,15 +983,15 @@ function createBridgeHost(options) {
|
|
|
946
983
|
}
|
|
947
984
|
const clean = sanitizeEvent(type, payload);
|
|
948
985
|
current = applyEvent(current, type, clean);
|
|
986
|
+
if (boundFrame === void 0) {
|
|
987
|
+
if (type === "visibility") pendingVisibility = clean;
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
949
990
|
const frame = iframe.contentWindow;
|
|
950
991
|
if (frame) post(frame, createEnvelope("evt", type, createSession(), clean));
|
|
951
992
|
},
|
|
952
993
|
dispose() {
|
|
953
|
-
|
|
954
|
-
disposed = true;
|
|
955
|
-
win.removeEventListener("message", onMessage);
|
|
956
|
-
if (resizeHandle !== void 0) cancelRaf(resizeHandle);
|
|
957
|
-
resizeHandle = void 0;
|
|
994
|
+
dispose("manual");
|
|
958
995
|
}
|
|
959
996
|
};
|
|
960
997
|
}
|
|
@@ -972,6 +1009,20 @@ function bridgeRuntime(g, makeFetch, config) {
|
|
|
972
1009
|
const session = config.session;
|
|
973
1010
|
const hostOrigin = config.hostOrigin;
|
|
974
1011
|
const parent = g.parent;
|
|
1012
|
+
const sendToHost = parent ? parent.postMessage.bind(parent) : void 0;
|
|
1013
|
+
const stopInterval = clearInterval;
|
|
1014
|
+
const stopTimeout = clearTimeout;
|
|
1015
|
+
let readPersisted;
|
|
1016
|
+
const ownDescriptor = Object.getOwnPropertyDescriptor;
|
|
1017
|
+
const hasOwn = Function.prototype.call.bind(Object.prototype.hasOwnProperty);
|
|
1018
|
+
try {
|
|
1019
|
+
const pte = g.PageTransitionEvent;
|
|
1020
|
+
const desc = pte ? Object.getOwnPropertyDescriptor(pte.prototype, "persisted") : void 0;
|
|
1021
|
+
if (desc && typeof desc.get === "function")
|
|
1022
|
+
readPersisted = Function.prototype.call.bind(desc.get);
|
|
1023
|
+
} catch {
|
|
1024
|
+
readPersisted = void 0;
|
|
1025
|
+
}
|
|
975
1026
|
const pending = /* @__PURE__ */ new Map();
|
|
976
1027
|
const listeners = /* @__PURE__ */ new Map();
|
|
977
1028
|
let init;
|
|
@@ -988,6 +1039,17 @@ function bridgeRuntime(g, makeFetch, config) {
|
|
|
988
1039
|
return out;
|
|
989
1040
|
}
|
|
990
1041
|
const idPrefix = randomPrefix();
|
|
1042
|
+
function secureToken() {
|
|
1043
|
+
if (!g.crypto || typeof g.crypto.getRandomValues !== "function") return "";
|
|
1044
|
+
const bytes = new Uint8Array(16);
|
|
1045
|
+
g.crypto.getRandomValues(bytes);
|
|
1046
|
+
let out = "";
|
|
1047
|
+
for (let i = 0; i < bytes.length; i += 1) out += (bytes[i] + 256).toString(16).slice(1);
|
|
1048
|
+
return out;
|
|
1049
|
+
}
|
|
1050
|
+
const frameToken = secureToken();
|
|
1051
|
+
let closed = frameToken === "";
|
|
1052
|
+
let unloaded = false;
|
|
991
1053
|
function nextId() {
|
|
992
1054
|
seq += 1;
|
|
993
1055
|
return idPrefix + "-" + seq;
|
|
@@ -999,9 +1061,12 @@ function bridgeRuntime(g, makeFetch, config) {
|
|
|
999
1061
|
return err;
|
|
1000
1062
|
}
|
|
1001
1063
|
function post(kind, type, id, payload) {
|
|
1002
|
-
if (!
|
|
1064
|
+
if (!sendToHost || closed) return;
|
|
1003
1065
|
const body = Object.assign({}, payload, { session });
|
|
1004
|
-
|
|
1066
|
+
sendToHost(
|
|
1067
|
+
{ v: 1, ns: NS, id, kind, type, payload: body, frame: frameToken },
|
|
1068
|
+
hostOrigin
|
|
1069
|
+
);
|
|
1005
1070
|
}
|
|
1006
1071
|
function isEnvelope(m) {
|
|
1007
1072
|
if (typeof m !== "object" || m === null) return false;
|
|
@@ -1074,6 +1139,54 @@ function bridgeRuntime(g, makeFetch, config) {
|
|
|
1074
1139
|
}
|
|
1075
1140
|
}
|
|
1076
1141
|
g.addEventListener("message", onMessage);
|
|
1142
|
+
function isPersisted(ev) {
|
|
1143
|
+
try {
|
|
1144
|
+
if (readPersisted) return readPersisted(ev) === true;
|
|
1145
|
+
if (typeof ev !== "object" || ev === null) return false;
|
|
1146
|
+
const desc = ownDescriptor(ev, "persisted");
|
|
1147
|
+
return !!desc && hasOwn(desc, "value") && desc.value === true;
|
|
1148
|
+
} catch {
|
|
1149
|
+
return false;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
function onPageHide(ev) {
|
|
1153
|
+
if (unloaded || isPersisted(ev)) return;
|
|
1154
|
+
unloaded = true;
|
|
1155
|
+
closed = true;
|
|
1156
|
+
if (sendToHost) {
|
|
1157
|
+
try {
|
|
1158
|
+
sendToHost(
|
|
1159
|
+
{
|
|
1160
|
+
v: 1,
|
|
1161
|
+
ns: NS,
|
|
1162
|
+
id: idPrefix + "-unload",
|
|
1163
|
+
kind: "evt",
|
|
1164
|
+
type: "frame-unload",
|
|
1165
|
+
payload: { session },
|
|
1166
|
+
frame: frameToken
|
|
1167
|
+
},
|
|
1168
|
+
hostOrigin
|
|
1169
|
+
);
|
|
1170
|
+
} catch {
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
try {
|
|
1174
|
+
stopInterval(readyTimer);
|
|
1175
|
+
} catch {
|
|
1176
|
+
}
|
|
1177
|
+
try {
|
|
1178
|
+
pending.forEach(function(entry) {
|
|
1179
|
+
try {
|
|
1180
|
+
stopTimeout(entry.timer);
|
|
1181
|
+
entry.reject(codeError("UNSUPPORTED", "bridge closed"));
|
|
1182
|
+
} catch {
|
|
1183
|
+
}
|
|
1184
|
+
});
|
|
1185
|
+
pending.clear();
|
|
1186
|
+
} catch {
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
g.addEventListener("pagehide", onPageHide, true);
|
|
1077
1190
|
let readyAttempts = 0;
|
|
1078
1191
|
function sendReady() {
|
|
1079
1192
|
readyAttempts += 1;
|
|
@@ -1087,6 +1200,10 @@ function bridgeRuntime(g, makeFetch, config) {
|
|
|
1087
1200
|
sendReady();
|
|
1088
1201
|
function request(type, payload) {
|
|
1089
1202
|
return new Promise(function(resolve, reject) {
|
|
1203
|
+
if (closed) {
|
|
1204
|
+
reject(codeError("UNSUPPORTED", "bridge closed"));
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1090
1207
|
const id = nextId();
|
|
1091
1208
|
const timer = setTimeout(function() {
|
|
1092
1209
|
pending.delete(id);
|
package/dist/host.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { f as BridgeHostEventType, e as BridgeEventMap,
|
|
2
|
-
export { a as BRIDGE_SURFACES, I as IFRAME_ATTRIBUTES,
|
|
1
|
+
import { f as BridgeHostEventType, e as BridgeEventMap, t as ToastKind, L as LogLevel, l as BridgeSurface, T as ThemeState, S as SlotState, M as MemberState, m as BridgeTranslations } from './messages-CRGnyf-6.cjs';
|
|
2
|
+
export { a as BRIDGE_SURFACES, I as IFRAME_ATTRIBUTES, w as isBridgeSurface } from './messages-CRGnyf-6.cjs';
|
|
3
3
|
export { C as CreateSrcdocOptions, c as createNonce, d as createSession, e as createSrcdoc, f as isValidSession } from './createSrcdoc-bOoxttPz.cjs';
|
|
4
4
|
|
|
5
5
|
/** 호스트 fetch 정책. */
|
|
@@ -213,11 +213,25 @@ interface CreateBridgeHostOptions {
|
|
|
213
213
|
readonly pinnedHeaders?: Readonly<Record<string, string>>;
|
|
214
214
|
readonly handlers?: BridgeHostHandlers;
|
|
215
215
|
readonly policy?: Partial<BridgePolicy>;
|
|
216
|
+
/**
|
|
217
|
+
* 호스트가 dispose될 때 한 번 불린다. `frame-unload`(프레임 문서가 내려감)·`frame-mismatch`(토큰이 없거나 다른
|
|
218
|
+
* 메시지 = 같은 창의 다른 문서)는 호스트가 스스로 끊은 경우, `manual`은 `dispose()` 호출이다. 임베더가 iframe을
|
|
219
|
+
* 바로 걷어 내는 데 쓴다. 이 콜백의 예외는 삼킨다.
|
|
220
|
+
*/
|
|
221
|
+
readonly onDispose?: (reason: BridgeDisposeReason) => void;
|
|
216
222
|
}
|
|
223
|
+
/** `onDispose` 사유. */
|
|
224
|
+
type BridgeDisposeReason = 'frame-unload' | 'frame-mismatch' | 'manual';
|
|
217
225
|
interface BridgeHost {
|
|
218
|
-
/**
|
|
226
|
+
/**
|
|
227
|
+
* 프레임에 이벤트를 보낸다. 이후 ready에 답하는 init에도 반영된다.
|
|
228
|
+
* 프레임 토큰이 묶이기(첫 유효 메시지) 전에는 보내지 않고 init에만 반영한다.
|
|
229
|
+
*/
|
|
219
230
|
send<E extends BridgeHostEventType>(type: E, payload: BridgeEventMap[E]): void;
|
|
220
|
-
/**
|
|
231
|
+
/**
|
|
232
|
+
* 리스너·대기 중인 resize를 정리한다. 이후 메시지는 무시되고, 진행 중이던 요청의 응답도 프레임에 보내지 않는다.
|
|
233
|
+
* 프레임이 `frame-unload`를 보내거나 다른 프레임 토큰이 오면 호스트가 스스로 부른다.
|
|
234
|
+
*/
|
|
221
235
|
dispose(): void;
|
|
222
236
|
}
|
|
223
237
|
|
|
@@ -226,7 +240,11 @@ interface BridgeHost {
|
|
|
226
240
|
*
|
|
227
241
|
* - 발신자 확인: `ev.source === iframe.contentWindow`, origin `"null"`(sandbox 프레임), 봉투 형식,
|
|
228
242
|
* `payload.session === session`이 모두 맞는 메시지만 처리한다.
|
|
229
|
-
* -
|
|
243
|
+
* - 세션당 프레임 문서는 하나다: 처음 받은 유효 메시지의 프레임 토큰(`env.frame`)에 세션을 묶고, 토큰이 없거나
|
|
244
|
+
* 다른 메시지(= 같은 contentWindow에 새로 뜬 문서, 예: 템플릿의 자기 내비게이션)가 오면 즉시 dispose한다.
|
|
245
|
+
* 그래서 두 번째 핸드셰이크는 성립하지 않는다.
|
|
246
|
+
* - `evt:frame-unload`(부트스트랩이 pagehide에서 보낸다)를 받으면 즉시 dispose한다.
|
|
247
|
+
* - `evt:ready`에 `res:init`으로 답한다(같은 토큰의 재전송이면 매번 최신 init으로).
|
|
230
248
|
* - 요청은 fetch 정책(policy.ts)·요청별 상한(validators.ts)을 적용해 처리기 또는 주입 `fetchImpl`로 넘긴다.
|
|
231
249
|
*
|
|
232
250
|
* 토큰은 이 함수가 다루지 않는다 — 인증은 `fetchImpl`(플랫폼의 인증 fetch)이 호스트 쪽에서 붙이고,
|
|
@@ -259,4 +277,4 @@ interface PathCheck {
|
|
|
259
277
|
*/
|
|
260
278
|
declare function checkPath(pathname: string, options?: PathCheckOptions): PathCheck;
|
|
261
279
|
|
|
262
|
-
export { ALLOWED_METHODS, ALLOWED_REQUEST_HEADERS, type BridgeHost, type BridgeHostHandlers, type BridgeInitInput, type BridgePolicy, BridgeSurface, type CreateBridgeHostOptions, DEFAULT_GATEWAY_DENY_SEGMENTS, DEFAULT_POLICY, type Destination, type DestinationContext, type DestinationKind, type FrameWindow, HOST_ONLY_HEADERS, type HostFetch, type HostWindow, type IframeLike, MAX_DECODE_PASSES, MAX_PARTNER_ORIGINS, type MessageEventLike, type PathCheck, type PathCheckOptions, type PreparedFetch, checkPath, classifyDestination, createBridgeHost, isAllowedGatewayPath, normalizePartnerOrigins, normalizePinnedHeaders, prepareFetch, resolveDestination, resolvePolicy };
|
|
280
|
+
export { ALLOWED_METHODS, ALLOWED_REQUEST_HEADERS, type BridgeDisposeReason, type BridgeHost, type BridgeHostHandlers, type BridgeInitInput, type BridgePolicy, BridgeSurface, type CreateBridgeHostOptions, DEFAULT_GATEWAY_DENY_SEGMENTS, DEFAULT_POLICY, type Destination, type DestinationContext, type DestinationKind, type FrameWindow, HOST_ONLY_HEADERS, type HostFetch, type HostWindow, type IframeLike, MAX_DECODE_PASSES, MAX_PARTNER_ORIGINS, type MessageEventLike, type PathCheck, type PathCheckOptions, type PreparedFetch, checkPath, classifyDestination, createBridgeHost, isAllowedGatewayPath, normalizePartnerOrigins, normalizePinnedHeaders, prepareFetch, resolveDestination, resolvePolicy };
|
package/dist/host.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { f as BridgeHostEventType, e as BridgeEventMap,
|
|
2
|
-
export { a as BRIDGE_SURFACES, I as IFRAME_ATTRIBUTES,
|
|
1
|
+
import { f as BridgeHostEventType, e as BridgeEventMap, t as ToastKind, L as LogLevel, l as BridgeSurface, T as ThemeState, S as SlotState, M as MemberState, m as BridgeTranslations } from './messages-CRGnyf-6.js';
|
|
2
|
+
export { a as BRIDGE_SURFACES, I as IFRAME_ATTRIBUTES, w as isBridgeSurface } from './messages-CRGnyf-6.js';
|
|
3
3
|
export { C as CreateSrcdocOptions, c as createNonce, d as createSession, e as createSrcdoc, f as isValidSession } from './createSrcdoc-bOoxttPz.js';
|
|
4
4
|
|
|
5
5
|
/** 호스트 fetch 정책. */
|
|
@@ -213,11 +213,25 @@ interface CreateBridgeHostOptions {
|
|
|
213
213
|
readonly pinnedHeaders?: Readonly<Record<string, string>>;
|
|
214
214
|
readonly handlers?: BridgeHostHandlers;
|
|
215
215
|
readonly policy?: Partial<BridgePolicy>;
|
|
216
|
+
/**
|
|
217
|
+
* 호스트가 dispose될 때 한 번 불린다. `frame-unload`(프레임 문서가 내려감)·`frame-mismatch`(토큰이 없거나 다른
|
|
218
|
+
* 메시지 = 같은 창의 다른 문서)는 호스트가 스스로 끊은 경우, `manual`은 `dispose()` 호출이다. 임베더가 iframe을
|
|
219
|
+
* 바로 걷어 내는 데 쓴다. 이 콜백의 예외는 삼킨다.
|
|
220
|
+
*/
|
|
221
|
+
readonly onDispose?: (reason: BridgeDisposeReason) => void;
|
|
216
222
|
}
|
|
223
|
+
/** `onDispose` 사유. */
|
|
224
|
+
type BridgeDisposeReason = 'frame-unload' | 'frame-mismatch' | 'manual';
|
|
217
225
|
interface BridgeHost {
|
|
218
|
-
/**
|
|
226
|
+
/**
|
|
227
|
+
* 프레임에 이벤트를 보낸다. 이후 ready에 답하는 init에도 반영된다.
|
|
228
|
+
* 프레임 토큰이 묶이기(첫 유효 메시지) 전에는 보내지 않고 init에만 반영한다.
|
|
229
|
+
*/
|
|
219
230
|
send<E extends BridgeHostEventType>(type: E, payload: BridgeEventMap[E]): void;
|
|
220
|
-
/**
|
|
231
|
+
/**
|
|
232
|
+
* 리스너·대기 중인 resize를 정리한다. 이후 메시지는 무시되고, 진행 중이던 요청의 응답도 프레임에 보내지 않는다.
|
|
233
|
+
* 프레임이 `frame-unload`를 보내거나 다른 프레임 토큰이 오면 호스트가 스스로 부른다.
|
|
234
|
+
*/
|
|
221
235
|
dispose(): void;
|
|
222
236
|
}
|
|
223
237
|
|
|
@@ -226,7 +240,11 @@ interface BridgeHost {
|
|
|
226
240
|
*
|
|
227
241
|
* - 발신자 확인: `ev.source === iframe.contentWindow`, origin `"null"`(sandbox 프레임), 봉투 형식,
|
|
228
242
|
* `payload.session === session`이 모두 맞는 메시지만 처리한다.
|
|
229
|
-
* -
|
|
243
|
+
* - 세션당 프레임 문서는 하나다: 처음 받은 유효 메시지의 프레임 토큰(`env.frame`)에 세션을 묶고, 토큰이 없거나
|
|
244
|
+
* 다른 메시지(= 같은 contentWindow에 새로 뜬 문서, 예: 템플릿의 자기 내비게이션)가 오면 즉시 dispose한다.
|
|
245
|
+
* 그래서 두 번째 핸드셰이크는 성립하지 않는다.
|
|
246
|
+
* - `evt:frame-unload`(부트스트랩이 pagehide에서 보낸다)를 받으면 즉시 dispose한다.
|
|
247
|
+
* - `evt:ready`에 `res:init`으로 답한다(같은 토큰의 재전송이면 매번 최신 init으로).
|
|
230
248
|
* - 요청은 fetch 정책(policy.ts)·요청별 상한(validators.ts)을 적용해 처리기 또는 주입 `fetchImpl`로 넘긴다.
|
|
231
249
|
*
|
|
232
250
|
* 토큰은 이 함수가 다루지 않는다 — 인증은 `fetchImpl`(플랫폼의 인증 fetch)이 호스트 쪽에서 붙이고,
|
|
@@ -259,4 +277,4 @@ interface PathCheck {
|
|
|
259
277
|
*/
|
|
260
278
|
declare function checkPath(pathname: string, options?: PathCheckOptions): PathCheck;
|
|
261
279
|
|
|
262
|
-
export { ALLOWED_METHODS, ALLOWED_REQUEST_HEADERS, type BridgeHost, type BridgeHostHandlers, type BridgeInitInput, type BridgePolicy, BridgeSurface, type CreateBridgeHostOptions, DEFAULT_GATEWAY_DENY_SEGMENTS, DEFAULT_POLICY, type Destination, type DestinationContext, type DestinationKind, type FrameWindow, HOST_ONLY_HEADERS, type HostFetch, type HostWindow, type IframeLike, MAX_DECODE_PASSES, MAX_PARTNER_ORIGINS, type MessageEventLike, type PathCheck, type PathCheckOptions, type PreparedFetch, checkPath, classifyDestination, createBridgeHost, isAllowedGatewayPath, normalizePartnerOrigins, normalizePinnedHeaders, prepareFetch, resolveDestination, resolvePolicy };
|
|
280
|
+
export { ALLOWED_METHODS, ALLOWED_REQUEST_HEADERS, type BridgeDisposeReason, type BridgeHost, type BridgeHostHandlers, type BridgeInitInput, type BridgePolicy, BridgeSurface, type CreateBridgeHostOptions, DEFAULT_GATEWAY_DENY_SEGMENTS, DEFAULT_POLICY, type Destination, type DestinationContext, type DestinationKind, type FrameWindow, HOST_ONLY_HEADERS, type HostFetch, type HostWindow, type IframeLike, MAX_DECODE_PASSES, MAX_PARTNER_ORIGINS, type MessageEventLike, type PathCheck, type PathCheckOptions, type PreparedFetch, checkPath, classifyDestination, createBridgeHost, isAllowedGatewayPath, normalizePartnerOrigins, normalizePinnedHeaders, prepareFetch, resolveDestination, resolvePolicy };
|
package/dist/host.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
BridgeError,
|
|
6
6
|
DEFAULT_GATEWAY_DENY_SEGMENTS,
|
|
7
7
|
DEFAULT_POLICY,
|
|
8
|
+
FRAME_UNLOAD_EVENT,
|
|
8
9
|
HOST_ONLY_HEADERS,
|
|
9
10
|
IFRAME_ATTRIBUTES,
|
|
10
11
|
INIT_RESPONSE,
|
|
@@ -31,8 +32,8 @@ import {
|
|
|
31
32
|
prepareFetch,
|
|
32
33
|
resolveDestination,
|
|
33
34
|
resolvePolicy
|
|
34
|
-
} from "./chunk-
|
|
35
|
-
import "./chunk-
|
|
35
|
+
} from "./chunk-JH3A3Z2A.js";
|
|
36
|
+
import "./chunk-N3JID7KQ.js";
|
|
36
37
|
|
|
37
38
|
// src/host/rateLimiter.ts
|
|
38
39
|
var DEFAULT_WINDOW_MS = 6e4;
|
|
@@ -453,6 +454,9 @@ function assertOptions(options) {
|
|
|
453
454
|
"[bstage-bridge] externalFetchImpl\uC5D0 partnerFetchImpl(\uD32C \uD1A0\uD070 fetch)\uC744 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4"
|
|
454
455
|
);
|
|
455
456
|
}
|
|
457
|
+
if (options.onDispose !== void 0 && typeof options.onDispose !== "function") {
|
|
458
|
+
throw new TypeError("[bstage-bridge] onDispose\uB294 \uD568\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4");
|
|
459
|
+
}
|
|
456
460
|
if (!options.iframe || !options.init) {
|
|
457
461
|
throw new TypeError("[bstage-bridge] iframe\xB7init\uC774 \uD544\uC694\uD569\uB2C8\uB2E4");
|
|
458
462
|
}
|
|
@@ -475,6 +479,8 @@ function createBridgeHost(options) {
|
|
|
475
479
|
let current = buildInit(options.init, hostOrigin, partnerOrigins, policy);
|
|
476
480
|
let inFlightFetches = 0;
|
|
477
481
|
let disposed = false;
|
|
482
|
+
let boundFrame;
|
|
483
|
+
let pendingVisibility;
|
|
478
484
|
let resizeHandle;
|
|
479
485
|
let pendingHeight;
|
|
480
486
|
const raf = typeof win.requestAnimationFrame === "function" ? (cb) => win.requestAnimationFrame(cb) : (cb) => setTimeout(cb, 16);
|
|
@@ -538,13 +544,44 @@ function createBridgeHost(options) {
|
|
|
538
544
|
const env = ev.data;
|
|
539
545
|
if (!isBridgeEnvelope(env)) return;
|
|
540
546
|
if (!hasSession(env.payload, session)) return;
|
|
547
|
+
const token = env.frame;
|
|
548
|
+
if (!isValidSession(token) || boundFrame !== void 0 && token !== boundFrame) {
|
|
549
|
+
dispose("frame-mismatch");
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
boundFrame = token;
|
|
553
|
+
if (env.kind === "evt" && env.type === FRAME_UNLOAD_EVENT) {
|
|
554
|
+
dispose("frame-unload");
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
541
557
|
if (env.kind === "evt" && env.type === READY_EVENT) {
|
|
542
558
|
if (!readyLimiter.take()) return;
|
|
543
559
|
post(frame, createEnvelope("res", INIT_RESPONSE, env.id, { ok: true, data: current }));
|
|
560
|
+
flushPendingVisibility(frame);
|
|
544
561
|
return;
|
|
545
562
|
}
|
|
563
|
+
flushPendingVisibility(frame);
|
|
546
564
|
if (env.kind === "req") void handleRequest(frame, env);
|
|
547
565
|
}
|
|
566
|
+
function flushPendingVisibility(frame) {
|
|
567
|
+
if (pendingVisibility === void 0) return;
|
|
568
|
+
const payload = pendingVisibility;
|
|
569
|
+
pendingVisibility = void 0;
|
|
570
|
+
post(frame, createEnvelope("evt", "visibility", createSession(), payload));
|
|
571
|
+
}
|
|
572
|
+
function dispose(reason) {
|
|
573
|
+
if (disposed) return;
|
|
574
|
+
disposed = true;
|
|
575
|
+
win.removeEventListener("message", onMessage);
|
|
576
|
+
if (resizeHandle !== void 0) cancelRaf(resizeHandle);
|
|
577
|
+
resizeHandle = void 0;
|
|
578
|
+
if (options.onDispose) {
|
|
579
|
+
try {
|
|
580
|
+
options.onDispose(reason);
|
|
581
|
+
} catch {
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
548
585
|
win.addEventListener("message", onMessage);
|
|
549
586
|
return {
|
|
550
587
|
send(type, payload) {
|
|
@@ -555,15 +592,15 @@ function createBridgeHost(options) {
|
|
|
555
592
|
}
|
|
556
593
|
const clean = sanitizeEvent(type, payload);
|
|
557
594
|
current = applyEvent(current, type, clean);
|
|
595
|
+
if (boundFrame === void 0) {
|
|
596
|
+
if (type === "visibility") pendingVisibility = clean;
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
558
599
|
const frame = iframe.contentWindow;
|
|
559
600
|
if (frame) post(frame, createEnvelope("evt", type, createSession(), clean));
|
|
560
601
|
},
|
|
561
602
|
dispose() {
|
|
562
|
-
|
|
563
|
-
disposed = true;
|
|
564
|
-
win.removeEventListener("message", onMessage);
|
|
565
|
-
if (resizeHandle !== void 0) cancelRaf(resizeHandle);
|
|
566
|
-
resizeHandle = void 0;
|
|
603
|
+
dispose("manual");
|
|
567
604
|
}
|
|
568
605
|
};
|
|
569
606
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -25,6 +25,7 @@ __export(src_exports, {
|
|
|
25
25
|
BRIDGE_SURFACES: () => BRIDGE_SURFACES,
|
|
26
26
|
BRIDGE_VERSION: () => BRIDGE_VERSION,
|
|
27
27
|
BridgeError: () => BridgeError,
|
|
28
|
+
FRAME_UNLOAD_EVENT: () => FRAME_UNLOAD_EVENT,
|
|
28
29
|
HOST_EVENT_TYPES: () => HOST_EVENT_TYPES,
|
|
29
30
|
IFRAME_ATTRIBUTES: () => IFRAME_ATTRIBUTES,
|
|
30
31
|
INIT_RESPONSE: () => INIT_RESPONSE,
|
|
@@ -114,6 +115,7 @@ var REQUEST_TYPES = [
|
|
|
114
115
|
var HOST_EVENT_TYPES = ["slot.context", "locale", "member", "theme", "visibility"];
|
|
115
116
|
var READY_EVENT = "ready";
|
|
116
117
|
var INIT_RESPONSE = "init";
|
|
118
|
+
var FRAME_UNLOAD_EVENT = "frame-unload";
|
|
117
119
|
var BRIDGE_SURFACES = ["page", "slot"];
|
|
118
120
|
function isBridgeSurface(value) {
|
|
119
121
|
return BRIDGE_SURFACES.includes(value);
|
|
@@ -165,6 +167,20 @@ function bridgeRuntime(g, makeFetch, config) {
|
|
|
165
167
|
const session = config.session;
|
|
166
168
|
const hostOrigin = config.hostOrigin;
|
|
167
169
|
const parent = g.parent;
|
|
170
|
+
const sendToHost = parent ? parent.postMessage.bind(parent) : void 0;
|
|
171
|
+
const stopInterval = clearInterval;
|
|
172
|
+
const stopTimeout = clearTimeout;
|
|
173
|
+
let readPersisted;
|
|
174
|
+
const ownDescriptor = Object.getOwnPropertyDescriptor;
|
|
175
|
+
const hasOwn = Function.prototype.call.bind(Object.prototype.hasOwnProperty);
|
|
176
|
+
try {
|
|
177
|
+
const pte = g.PageTransitionEvent;
|
|
178
|
+
const desc = pte ? Object.getOwnPropertyDescriptor(pte.prototype, "persisted") : void 0;
|
|
179
|
+
if (desc && typeof desc.get === "function")
|
|
180
|
+
readPersisted = Function.prototype.call.bind(desc.get);
|
|
181
|
+
} catch {
|
|
182
|
+
readPersisted = void 0;
|
|
183
|
+
}
|
|
168
184
|
const pending = /* @__PURE__ */ new Map();
|
|
169
185
|
const listeners = /* @__PURE__ */ new Map();
|
|
170
186
|
let init;
|
|
@@ -181,6 +197,17 @@ function bridgeRuntime(g, makeFetch, config) {
|
|
|
181
197
|
return out;
|
|
182
198
|
}
|
|
183
199
|
const idPrefix = randomPrefix();
|
|
200
|
+
function secureToken() {
|
|
201
|
+
if (!g.crypto || typeof g.crypto.getRandomValues !== "function") return "";
|
|
202
|
+
const bytes = new Uint8Array(16);
|
|
203
|
+
g.crypto.getRandomValues(bytes);
|
|
204
|
+
let out = "";
|
|
205
|
+
for (let i = 0; i < bytes.length; i += 1) out += (bytes[i] + 256).toString(16).slice(1);
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
const frameToken = secureToken();
|
|
209
|
+
let closed = frameToken === "";
|
|
210
|
+
let unloaded = false;
|
|
184
211
|
function nextId() {
|
|
185
212
|
seq += 1;
|
|
186
213
|
return idPrefix + "-" + seq;
|
|
@@ -192,9 +219,12 @@ function bridgeRuntime(g, makeFetch, config) {
|
|
|
192
219
|
return err;
|
|
193
220
|
}
|
|
194
221
|
function post(kind, type, id, payload) {
|
|
195
|
-
if (!
|
|
222
|
+
if (!sendToHost || closed) return;
|
|
196
223
|
const body = Object.assign({}, payload, { session });
|
|
197
|
-
|
|
224
|
+
sendToHost(
|
|
225
|
+
{ v: 1, ns: NS, id, kind, type, payload: body, frame: frameToken },
|
|
226
|
+
hostOrigin
|
|
227
|
+
);
|
|
198
228
|
}
|
|
199
229
|
function isEnvelope(m) {
|
|
200
230
|
if (typeof m !== "object" || m === null) return false;
|
|
@@ -267,6 +297,54 @@ function bridgeRuntime(g, makeFetch, config) {
|
|
|
267
297
|
}
|
|
268
298
|
}
|
|
269
299
|
g.addEventListener("message", onMessage);
|
|
300
|
+
function isPersisted(ev) {
|
|
301
|
+
try {
|
|
302
|
+
if (readPersisted) return readPersisted(ev) === true;
|
|
303
|
+
if (typeof ev !== "object" || ev === null) return false;
|
|
304
|
+
const desc = ownDescriptor(ev, "persisted");
|
|
305
|
+
return !!desc && hasOwn(desc, "value") && desc.value === true;
|
|
306
|
+
} catch {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function onPageHide(ev) {
|
|
311
|
+
if (unloaded || isPersisted(ev)) return;
|
|
312
|
+
unloaded = true;
|
|
313
|
+
closed = true;
|
|
314
|
+
if (sendToHost) {
|
|
315
|
+
try {
|
|
316
|
+
sendToHost(
|
|
317
|
+
{
|
|
318
|
+
v: 1,
|
|
319
|
+
ns: NS,
|
|
320
|
+
id: idPrefix + "-unload",
|
|
321
|
+
kind: "evt",
|
|
322
|
+
type: "frame-unload",
|
|
323
|
+
payload: { session },
|
|
324
|
+
frame: frameToken
|
|
325
|
+
},
|
|
326
|
+
hostOrigin
|
|
327
|
+
);
|
|
328
|
+
} catch {
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
try {
|
|
332
|
+
stopInterval(readyTimer);
|
|
333
|
+
} catch {
|
|
334
|
+
}
|
|
335
|
+
try {
|
|
336
|
+
pending.forEach(function(entry) {
|
|
337
|
+
try {
|
|
338
|
+
stopTimeout(entry.timer);
|
|
339
|
+
entry.reject(codeError("UNSUPPORTED", "bridge closed"));
|
|
340
|
+
} catch {
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
pending.clear();
|
|
344
|
+
} catch {
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
g.addEventListener("pagehide", onPageHide, true);
|
|
270
348
|
let readyAttempts = 0;
|
|
271
349
|
function sendReady() {
|
|
272
350
|
readyAttempts += 1;
|
|
@@ -280,6 +358,10 @@ function bridgeRuntime(g, makeFetch, config) {
|
|
|
280
358
|
sendReady();
|
|
281
359
|
function request(type, payload) {
|
|
282
360
|
return new Promise(function(resolve, reject) {
|
|
361
|
+
if (closed) {
|
|
362
|
+
reject(codeError("UNSUPPORTED", "bridge closed"));
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
283
365
|
const id = nextId();
|
|
284
366
|
const timer = setTimeout(function() {
|
|
285
367
|
pending.delete(id);
|
|
@@ -646,6 +728,7 @@ ${script}
|
|
|
646
728
|
BRIDGE_SURFACES,
|
|
647
729
|
BRIDGE_VERSION,
|
|
648
730
|
BridgeError,
|
|
731
|
+
FRAME_UNLOAD_EVENT,
|
|
649
732
|
HOST_EVENT_TYPES,
|
|
650
733
|
IFRAME_ATTRIBUTES,
|
|
651
734
|
INIT_RESPONSE,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { B as BRIDGE_ERROR_CODES, a as BRIDGE_SURFACES, b as BridgeError, c as BridgeErrorCode, d as BridgeErrorLike, e as BridgeEventMap, f as BridgeHostEventType, g as BridgeInit, h as BridgeLimits, i as BridgeRequestMap, j as BridgeRequestType, k as BridgeResult, l as BridgeSurface, m as BridgeTranslations, F as
|
|
1
|
+
export { B as BRIDGE_ERROR_CODES, a as BRIDGE_SURFACES, b as BridgeError, c as BridgeErrorCode, d as BridgeErrorLike, e as BridgeEventMap, f as BridgeHostEventType, g as BridgeInit, h as BridgeLimits, i as BridgeRequestMap, j as BridgeRequestType, k as BridgeResult, l as BridgeSurface, m as BridgeTranslations, F as FRAME_UNLOAD_EVENT, n as FetchRequest, o as FetchResult, H as HOST_EVENT_TYPES, I as IFRAME_ATTRIBUTES, p as INIT_RESPONSE, L as LogLevel, M as MemberState, R as READY_EVENT, q as REQUEST_TIMEOUT_MS, r as REQUEST_TYPES, S as SlotState, s as StorageOp, T as ThemeState, t as ToastKind, W as WithSession, u as isBridgeErrorCode, v as isBridgeErrorLike, w as isBridgeSurface, x as isHostEventType, y as isRequestType } from './messages-CRGnyf-6.cjs';
|
|
2
2
|
export { C as CreateSrcdocOptions, M as MAX_ELEMENT_NAME_LENGTH, a as MAX_MEDIA_ORIGINS, b as buildFrameCsp, c as createNonce, d as createSession, e as createSrcdoc, i as isValidNonce, f as isValidSession } from './createSrcdoc-bOoxttPz.cjs';
|
|
3
3
|
|
|
4
4
|
/** 봉투 버전. 형식이 하위 호환되지 않게 바뀔 때만 올린다. */
|
|
@@ -19,6 +19,11 @@ interface BridgeEnvelope<P = unknown> {
|
|
|
19
19
|
readonly kind: EnvelopeKind;
|
|
20
20
|
readonly type: string;
|
|
21
21
|
readonly payload: P;
|
|
22
|
+
/**
|
|
23
|
+
* 프레임 → 호스트 봉투에만 붙는 프레임 토큰(소문자 hex 32자). 프레임 런타임이 문서마다 새로 만들고
|
|
24
|
+
* 호스트는 처음 본 값에 세션을 묶는다. 호스트 → 프레임 봉투에는 없다.
|
|
25
|
+
*/
|
|
26
|
+
readonly frame?: string;
|
|
22
27
|
}
|
|
23
28
|
/**
|
|
24
29
|
* 값이 브리지 봉투인지 검사한다. 모양만 본다 — 발신자 확인(source·session)은 호출자 몫이다.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { B as BRIDGE_ERROR_CODES, a as BRIDGE_SURFACES, b as BridgeError, c as BridgeErrorCode, d as BridgeErrorLike, e as BridgeEventMap, f as BridgeHostEventType, g as BridgeInit, h as BridgeLimits, i as BridgeRequestMap, j as BridgeRequestType, k as BridgeResult, l as BridgeSurface, m as BridgeTranslations, F as
|
|
1
|
+
export { B as BRIDGE_ERROR_CODES, a as BRIDGE_SURFACES, b as BridgeError, c as BridgeErrorCode, d as BridgeErrorLike, e as BridgeEventMap, f as BridgeHostEventType, g as BridgeInit, h as BridgeLimits, i as BridgeRequestMap, j as BridgeRequestType, k as BridgeResult, l as BridgeSurface, m as BridgeTranslations, F as FRAME_UNLOAD_EVENT, n as FetchRequest, o as FetchResult, H as HOST_EVENT_TYPES, I as IFRAME_ATTRIBUTES, p as INIT_RESPONSE, L as LogLevel, M as MemberState, R as READY_EVENT, q as REQUEST_TIMEOUT_MS, r as REQUEST_TYPES, S as SlotState, s as StorageOp, T as ThemeState, t as ToastKind, W as WithSession, u as isBridgeErrorCode, v as isBridgeErrorLike, w as isBridgeSurface, x as isHostEventType, y as isRequestType } from './messages-CRGnyf-6.js';
|
|
2
2
|
export { C as CreateSrcdocOptions, M as MAX_ELEMENT_NAME_LENGTH, a as MAX_MEDIA_ORIGINS, b as buildFrameCsp, c as createNonce, d as createSession, e as createSrcdoc, i as isValidNonce, f as isValidSession } from './createSrcdoc-bOoxttPz.js';
|
|
3
3
|
|
|
4
4
|
/** 봉투 버전. 형식이 하위 호환되지 않게 바뀔 때만 올린다. */
|
|
@@ -19,6 +19,11 @@ interface BridgeEnvelope<P = unknown> {
|
|
|
19
19
|
readonly kind: EnvelopeKind;
|
|
20
20
|
readonly type: string;
|
|
21
21
|
readonly payload: P;
|
|
22
|
+
/**
|
|
23
|
+
* 프레임 → 호스트 봉투에만 붙는 프레임 토큰(소문자 hex 32자). 프레임 런타임이 문서마다 새로 만들고
|
|
24
|
+
* 호스트는 처음 본 값에 세션을 묶는다. 호스트 → 프레임 봉투에는 없다.
|
|
25
|
+
*/
|
|
26
|
+
readonly frame?: string;
|
|
22
27
|
}
|
|
23
28
|
/**
|
|
24
29
|
* 값이 브리지 봉투인지 검사한다. 모양만 본다 — 발신자 확인(source·session)은 호출자 몫이다.
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
BRIDGE_SURFACES,
|
|
5
5
|
BRIDGE_VERSION,
|
|
6
6
|
BridgeError,
|
|
7
|
+
FRAME_UNLOAD_EVENT,
|
|
7
8
|
HOST_EVENT_TYPES,
|
|
8
9
|
IFRAME_ATTRIBUTES,
|
|
9
10
|
INIT_RESPONSE,
|
|
@@ -28,14 +29,15 @@ import {
|
|
|
28
29
|
isRequestType,
|
|
29
30
|
isValidNonce,
|
|
30
31
|
isValidSession
|
|
31
|
-
} from "./chunk-
|
|
32
|
-
import "./chunk-
|
|
32
|
+
} from "./chunk-JH3A3Z2A.js";
|
|
33
|
+
import "./chunk-N3JID7KQ.js";
|
|
33
34
|
export {
|
|
34
35
|
BRIDGE_ERROR_CODES,
|
|
35
36
|
BRIDGE_NAMESPACE,
|
|
36
37
|
BRIDGE_SURFACES,
|
|
37
38
|
BRIDGE_VERSION,
|
|
38
39
|
BridgeError,
|
|
40
|
+
FRAME_UNLOAD_EVENT,
|
|
39
41
|
HOST_EVENT_TYPES,
|
|
40
42
|
IFRAME_ATTRIBUTES,
|
|
41
43
|
INIT_RESPONSE,
|
|
@@ -28,6 +28,11 @@ type BridgeHostEventType = (typeof HOST_EVENT_TYPES)[number];
|
|
|
28
28
|
declare const READY_EVENT = "ready";
|
|
29
29
|
/** 호스트 → 프레임 핸드셰이크 응답 type. */
|
|
30
30
|
declare const INIT_RESPONSE = "init";
|
|
31
|
+
/**
|
|
32
|
+
* 프레임 → 호스트 종료 알림 이벤트. 부트스트랩이 `pagehide`(bfcache 제외)에서 동기로 보낸다.
|
|
33
|
+
* 호스트는 받는 즉시 세션을 끊는다(이후 메시지 무시).
|
|
34
|
+
*/
|
|
35
|
+
declare const FRAME_UNLOAD_EVENT = "frame-unload";
|
|
31
36
|
/** 프레임이 보내는 모든 payload에 붙는 세션. */
|
|
32
37
|
type WithSession<T> = T & {
|
|
33
38
|
readonly session: string;
|
|
@@ -183,4 +188,4 @@ declare const IFRAME_ATTRIBUTES: {
|
|
|
183
188
|
declare function isRequestType(value: unknown): value is BridgeRequestType;
|
|
184
189
|
declare function isHostEventType(value: unknown): value is BridgeHostEventType;
|
|
185
190
|
|
|
186
|
-
export { BRIDGE_ERROR_CODES as B,
|
|
191
|
+
export { BRIDGE_ERROR_CODES as B, FRAME_UNLOAD_EVENT as F, HOST_EVENT_TYPES as H, IFRAME_ATTRIBUTES as I, type LogLevel as L, type MemberState as M, READY_EVENT as R, type SlotState as S, type ThemeState as T, type WithSession as W, BRIDGE_SURFACES as a, BridgeError as b, type BridgeErrorCode as c, type BridgeErrorLike as d, type BridgeEventMap as e, type BridgeHostEventType as f, type BridgeInit as g, type BridgeLimits as h, type BridgeRequestMap as i, type BridgeRequestType as j, type BridgeResult as k, type BridgeSurface as l, type BridgeTranslations as m, type FetchRequest as n, type FetchResult as o, INIT_RESPONSE as p, REQUEST_TIMEOUT_MS as q, REQUEST_TYPES as r, type StorageOp as s, type ToastKind as t, isBridgeErrorCode as u, isBridgeErrorLike as v, isBridgeSurface as w, isHostEventType as x, isRequestType as y };
|
|
@@ -28,6 +28,11 @@ type BridgeHostEventType = (typeof HOST_EVENT_TYPES)[number];
|
|
|
28
28
|
declare const READY_EVENT = "ready";
|
|
29
29
|
/** 호스트 → 프레임 핸드셰이크 응답 type. */
|
|
30
30
|
declare const INIT_RESPONSE = "init";
|
|
31
|
+
/**
|
|
32
|
+
* 프레임 → 호스트 종료 알림 이벤트. 부트스트랩이 `pagehide`(bfcache 제외)에서 동기로 보낸다.
|
|
33
|
+
* 호스트는 받는 즉시 세션을 끊는다(이후 메시지 무시).
|
|
34
|
+
*/
|
|
35
|
+
declare const FRAME_UNLOAD_EVENT = "frame-unload";
|
|
31
36
|
/** 프레임이 보내는 모든 payload에 붙는 세션. */
|
|
32
37
|
type WithSession<T> = T & {
|
|
33
38
|
readonly session: string;
|
|
@@ -183,4 +188,4 @@ declare const IFRAME_ATTRIBUTES: {
|
|
|
183
188
|
declare function isRequestType(value: unknown): value is BridgeRequestType;
|
|
184
189
|
declare function isHostEventType(value: unknown): value is BridgeHostEventType;
|
|
185
190
|
|
|
186
|
-
export { BRIDGE_ERROR_CODES as B,
|
|
191
|
+
export { BRIDGE_ERROR_CODES as B, FRAME_UNLOAD_EVENT as F, HOST_EVENT_TYPES as H, IFRAME_ATTRIBUTES as I, type LogLevel as L, type MemberState as M, READY_EVENT as R, type SlotState as S, type ThemeState as T, type WithSession as W, BRIDGE_SURFACES as a, BridgeError as b, type BridgeErrorCode as c, type BridgeErrorLike as d, type BridgeEventMap as e, type BridgeHostEventType as f, type BridgeInit as g, type BridgeLimits as h, type BridgeRequestMap as i, type BridgeRequestType as j, type BridgeResult as k, type BridgeSurface as l, type BridgeTranslations as m, type FetchRequest as n, type FetchResult as o, INIT_RESPONSE as p, REQUEST_TIMEOUT_MS as q, REQUEST_TYPES as r, type StorageOp as s, type ToastKind as t, isBridgeErrorCode as u, isBridgeErrorLike as v, isBridgeSurface as w, isHostEventType as x, isRequestType as y };
|