@bstage-sdk/bridge 0.9.1 → 0.11.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 +110 -4
- package/dist/{chunk-KQMJY4ZH.js → chunk-CSAW3L7P.js} +204 -3
- package/dist/{chunk-6LV7HEN4.js → chunk-ND2HFYBP.js} +9 -3
- package/dist/{createSrcdoc-bOoxttPz.d.cts → createSrcdoc-DgQyvIjG.d.cts} +3 -0
- package/dist/{createSrcdoc-bOoxttPz.d.ts → createSrcdoc-DgQyvIjG.d.ts} +3 -0
- package/dist/frame.cjs +206 -4
- package/dist/frame.d.cts +52 -7
- package/dist/frame.d.ts +52 -7
- package/dist/frame.js +5 -3
- package/dist/host.cjs +249 -8
- package/dist/host.d.cts +25 -7
- package/dist/host.d.ts +25 -7
- package/dist/host.js +45 -7
- package/dist/index.cjs +209 -3
- package/dist/index.d.cts +7 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +4 -2
- package/dist/{messages-9N0GFq5b.d.cts → messages-BjpZyiLy.d.cts} +12 -1
- package/dist/{messages-9N0GFq5b.d.ts → messages-BjpZyiLy.d.ts} +12 -1
- package/package.json +1 -1
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);
|
|
@@ -505,6 +506,7 @@ function buildInit(init, hostOrigin, partnerOrigins, policy) {
|
|
|
505
506
|
spaceId: String(init.spaceId),
|
|
506
507
|
hostOrigin,
|
|
507
508
|
gatewayBase: `${hostOrigin}/gw`,
|
|
509
|
+
gatewayPrefixes: policy.gatewayPrefixes,
|
|
508
510
|
locale: String(init.locale),
|
|
509
511
|
theme: sanitizeTheme(init.theme),
|
|
510
512
|
...slot ? { slot } : {},
|
|
@@ -844,6 +846,9 @@ function assertOptions(options) {
|
|
|
844
846
|
"[bstage-bridge] externalFetchImpl\uC5D0 partnerFetchImpl(\uD32C \uD1A0\uD070 fetch)\uC744 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4"
|
|
845
847
|
);
|
|
846
848
|
}
|
|
849
|
+
if (options.onDispose !== void 0 && typeof options.onDispose !== "function") {
|
|
850
|
+
throw new TypeError("[bstage-bridge] onDispose\uB294 \uD568\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4");
|
|
851
|
+
}
|
|
847
852
|
if (!options.iframe || !options.init) {
|
|
848
853
|
throw new TypeError("[bstage-bridge] iframe\xB7init\uC774 \uD544\uC694\uD569\uB2C8\uB2E4");
|
|
849
854
|
}
|
|
@@ -866,6 +871,8 @@ function createBridgeHost(options) {
|
|
|
866
871
|
let current = buildInit(options.init, hostOrigin, partnerOrigins, policy);
|
|
867
872
|
let inFlightFetches = 0;
|
|
868
873
|
let disposed = false;
|
|
874
|
+
let boundFrame;
|
|
875
|
+
let pendingVisibility;
|
|
869
876
|
let resizeHandle;
|
|
870
877
|
let pendingHeight;
|
|
871
878
|
const raf = typeof win.requestAnimationFrame === "function" ? (cb) => win.requestAnimationFrame(cb) : (cb) => setTimeout(cb, 16);
|
|
@@ -929,13 +936,44 @@ function createBridgeHost(options) {
|
|
|
929
936
|
const env = ev.data;
|
|
930
937
|
if (!isBridgeEnvelope(env)) return;
|
|
931
938
|
if (!hasSession(env.payload, session)) return;
|
|
939
|
+
const token = env.frame;
|
|
940
|
+
if (!isValidSession(token) || boundFrame !== void 0 && token !== boundFrame) {
|
|
941
|
+
dispose("frame-mismatch");
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
boundFrame = token;
|
|
945
|
+
if (env.kind === "evt" && env.type === FRAME_UNLOAD_EVENT) {
|
|
946
|
+
dispose("frame-unload");
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
932
949
|
if (env.kind === "evt" && env.type === READY_EVENT) {
|
|
933
950
|
if (!readyLimiter.take()) return;
|
|
934
951
|
post(frame, createEnvelope("res", INIT_RESPONSE, env.id, { ok: true, data: current }));
|
|
952
|
+
flushPendingVisibility(frame);
|
|
935
953
|
return;
|
|
936
954
|
}
|
|
955
|
+
flushPendingVisibility(frame);
|
|
937
956
|
if (env.kind === "req") void handleRequest(frame, env);
|
|
938
957
|
}
|
|
958
|
+
function flushPendingVisibility(frame) {
|
|
959
|
+
if (pendingVisibility === void 0) return;
|
|
960
|
+
const payload = pendingVisibility;
|
|
961
|
+
pendingVisibility = void 0;
|
|
962
|
+
post(frame, createEnvelope("evt", "visibility", createSession(), payload));
|
|
963
|
+
}
|
|
964
|
+
function dispose(reason) {
|
|
965
|
+
if (disposed) return;
|
|
966
|
+
disposed = true;
|
|
967
|
+
win.removeEventListener("message", onMessage);
|
|
968
|
+
if (resizeHandle !== void 0) cancelRaf(resizeHandle);
|
|
969
|
+
resizeHandle = void 0;
|
|
970
|
+
if (options.onDispose) {
|
|
971
|
+
try {
|
|
972
|
+
options.onDispose(reason);
|
|
973
|
+
} catch {
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
939
977
|
win.addEventListener("message", onMessage);
|
|
940
978
|
return {
|
|
941
979
|
send(type, payload) {
|
|
@@ -946,15 +984,15 @@ function createBridgeHost(options) {
|
|
|
946
984
|
}
|
|
947
985
|
const clean = sanitizeEvent(type, payload);
|
|
948
986
|
current = applyEvent(current, type, clean);
|
|
987
|
+
if (boundFrame === void 0) {
|
|
988
|
+
if (type === "visibility") pendingVisibility = clean;
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
949
991
|
const frame = iframe.contentWindow;
|
|
950
992
|
if (frame) post(frame, createEnvelope("evt", type, createSession(), clean));
|
|
951
993
|
},
|
|
952
994
|
dispose() {
|
|
953
|
-
|
|
954
|
-
disposed = true;
|
|
955
|
-
win.removeEventListener("message", onMessage);
|
|
956
|
-
if (resizeHandle !== void 0) cancelRaf(resizeHandle);
|
|
957
|
-
resizeHandle = void 0;
|
|
995
|
+
dispose("manual");
|
|
958
996
|
}
|
|
959
997
|
};
|
|
960
998
|
}
|
|
@@ -972,6 +1010,20 @@ function bridgeRuntime(g, makeFetch, config) {
|
|
|
972
1010
|
const session = config.session;
|
|
973
1011
|
const hostOrigin = config.hostOrigin;
|
|
974
1012
|
const parent = g.parent;
|
|
1013
|
+
const sendToHost = parent ? parent.postMessage.bind(parent) : void 0;
|
|
1014
|
+
const stopInterval = clearInterval;
|
|
1015
|
+
const stopTimeout = clearTimeout;
|
|
1016
|
+
let readPersisted;
|
|
1017
|
+
const ownDescriptor = Object.getOwnPropertyDescriptor;
|
|
1018
|
+
const hasOwn = Function.prototype.call.bind(Object.prototype.hasOwnProperty);
|
|
1019
|
+
try {
|
|
1020
|
+
const pte = g.PageTransitionEvent;
|
|
1021
|
+
const desc = pte ? Object.getOwnPropertyDescriptor(pte.prototype, "persisted") : void 0;
|
|
1022
|
+
if (desc && typeof desc.get === "function")
|
|
1023
|
+
readPersisted = Function.prototype.call.bind(desc.get);
|
|
1024
|
+
} catch {
|
|
1025
|
+
readPersisted = void 0;
|
|
1026
|
+
}
|
|
975
1027
|
const pending = /* @__PURE__ */ new Map();
|
|
976
1028
|
const listeners = /* @__PURE__ */ new Map();
|
|
977
1029
|
let init;
|
|
@@ -988,6 +1040,17 @@ function bridgeRuntime(g, makeFetch, config) {
|
|
|
988
1040
|
return out;
|
|
989
1041
|
}
|
|
990
1042
|
const idPrefix = randomPrefix();
|
|
1043
|
+
function secureToken() {
|
|
1044
|
+
if (!g.crypto || typeof g.crypto.getRandomValues !== "function") return "";
|
|
1045
|
+
const bytes = new Uint8Array(16);
|
|
1046
|
+
g.crypto.getRandomValues(bytes);
|
|
1047
|
+
let out = "";
|
|
1048
|
+
for (let i = 0; i < bytes.length; i += 1) out += (bytes[i] + 256).toString(16).slice(1);
|
|
1049
|
+
return out;
|
|
1050
|
+
}
|
|
1051
|
+
const frameToken = secureToken();
|
|
1052
|
+
let closed = frameToken === "";
|
|
1053
|
+
let unloaded = false;
|
|
991
1054
|
function nextId() {
|
|
992
1055
|
seq += 1;
|
|
993
1056
|
return idPrefix + "-" + seq;
|
|
@@ -999,9 +1062,12 @@ function bridgeRuntime(g, makeFetch, config) {
|
|
|
999
1062
|
return err;
|
|
1000
1063
|
}
|
|
1001
1064
|
function post(kind, type, id, payload) {
|
|
1002
|
-
if (!
|
|
1065
|
+
if (!sendToHost || closed) return;
|
|
1003
1066
|
const body = Object.assign({}, payload, { session });
|
|
1004
|
-
|
|
1067
|
+
sendToHost(
|
|
1068
|
+
{ v: 1, ns: NS, id, kind, type, payload: body, frame: frameToken },
|
|
1069
|
+
hostOrigin
|
|
1070
|
+
);
|
|
1005
1071
|
}
|
|
1006
1072
|
function isEnvelope(m) {
|
|
1007
1073
|
if (typeof m !== "object" || m === null) return false;
|
|
@@ -1074,6 +1140,54 @@ function bridgeRuntime(g, makeFetch, config) {
|
|
|
1074
1140
|
}
|
|
1075
1141
|
}
|
|
1076
1142
|
g.addEventListener("message", onMessage);
|
|
1143
|
+
function isPersisted(ev) {
|
|
1144
|
+
try {
|
|
1145
|
+
if (readPersisted) return readPersisted(ev) === true;
|
|
1146
|
+
if (typeof ev !== "object" || ev === null) return false;
|
|
1147
|
+
const desc = ownDescriptor(ev, "persisted");
|
|
1148
|
+
return !!desc && hasOwn(desc, "value") && desc.value === true;
|
|
1149
|
+
} catch {
|
|
1150
|
+
return false;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
function onPageHide(ev) {
|
|
1154
|
+
if (unloaded || isPersisted(ev)) return;
|
|
1155
|
+
unloaded = true;
|
|
1156
|
+
closed = true;
|
|
1157
|
+
if (sendToHost) {
|
|
1158
|
+
try {
|
|
1159
|
+
sendToHost(
|
|
1160
|
+
{
|
|
1161
|
+
v: 1,
|
|
1162
|
+
ns: NS,
|
|
1163
|
+
id: idPrefix + "-unload",
|
|
1164
|
+
kind: "evt",
|
|
1165
|
+
type: "frame-unload",
|
|
1166
|
+
payload: { session },
|
|
1167
|
+
frame: frameToken
|
|
1168
|
+
},
|
|
1169
|
+
hostOrigin
|
|
1170
|
+
);
|
|
1171
|
+
} catch {
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
try {
|
|
1175
|
+
stopInterval(readyTimer);
|
|
1176
|
+
} catch {
|
|
1177
|
+
}
|
|
1178
|
+
try {
|
|
1179
|
+
pending.forEach(function(entry) {
|
|
1180
|
+
try {
|
|
1181
|
+
stopTimeout(entry.timer);
|
|
1182
|
+
entry.reject(codeError("UNSUPPORTED", "bridge closed"));
|
|
1183
|
+
} catch {
|
|
1184
|
+
}
|
|
1185
|
+
});
|
|
1186
|
+
pending.clear();
|
|
1187
|
+
} catch {
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
g.addEventListener("pagehide", onPageHide, true);
|
|
1077
1191
|
let readyAttempts = 0;
|
|
1078
1192
|
function sendReady() {
|
|
1079
1193
|
readyAttempts += 1;
|
|
@@ -1087,6 +1201,10 @@ function bridgeRuntime(g, makeFetch, config) {
|
|
|
1087
1201
|
sendReady();
|
|
1088
1202
|
function request(type, payload) {
|
|
1089
1203
|
return new Promise(function(resolve, reject) {
|
|
1204
|
+
if (closed) {
|
|
1205
|
+
reject(codeError("UNSUPPORTED", "bridge closed"));
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1090
1208
|
const id = nextId();
|
|
1091
1209
|
const timer = setTimeout(function() {
|
|
1092
1210
|
pending.delete(id);
|
|
@@ -1221,6 +1339,126 @@ function createBridgeFetch(client) {
|
|
|
1221
1339
|
return bridgeFetch;
|
|
1222
1340
|
}
|
|
1223
1341
|
|
|
1342
|
+
// src/frame/directFetch.ts
|
|
1343
|
+
function installDirectFetch(g, client, config) {
|
|
1344
|
+
const hostOrigin = config.hostOrigin;
|
|
1345
|
+
const callFn = Function.prototype.call;
|
|
1346
|
+
const NativeURL = g.URL;
|
|
1347
|
+
const rawFetch = g.fetch;
|
|
1348
|
+
const nativeFetch = typeof rawFetch === "function" ? callFn.bind(rawFetch) : void 0;
|
|
1349
|
+
const bridgeFetch = client.fetch;
|
|
1350
|
+
const strSlice = callFn.bind(String.prototype.slice);
|
|
1351
|
+
const promiseThen = callFn.bind(Promise.prototype.then);
|
|
1352
|
+
const promiseReject = Promise.reject.bind(Promise);
|
|
1353
|
+
const assign = Object.assign;
|
|
1354
|
+
const TypeErr = TypeError;
|
|
1355
|
+
function getter(ctor, name) {
|
|
1356
|
+
if (typeof ctor !== "function") return void 0;
|
|
1357
|
+
let proto = ctor.prototype;
|
|
1358
|
+
while (proto) {
|
|
1359
|
+
const desc = Object.getOwnPropertyDescriptor(proto, name);
|
|
1360
|
+
if (desc && typeof desc.get === "function") {
|
|
1361
|
+
return callFn.bind(desc.get);
|
|
1362
|
+
}
|
|
1363
|
+
proto = Object.getPrototypeOf(proto);
|
|
1364
|
+
}
|
|
1365
|
+
return void 0;
|
|
1366
|
+
}
|
|
1367
|
+
const getHref = getter(NativeURL, "href");
|
|
1368
|
+
const getOrigin = getter(NativeURL, "origin");
|
|
1369
|
+
const getProtocol = getter(NativeURL, "protocol");
|
|
1370
|
+
const getPathname = getter(NativeURL, "pathname");
|
|
1371
|
+
const getRequestUrl = getter(g.Request, "url");
|
|
1372
|
+
let gatewayPrefixes = null;
|
|
1373
|
+
let partnerOrigins = [];
|
|
1374
|
+
let routesReady = false;
|
|
1375
|
+
function copyStrings(value) {
|
|
1376
|
+
if (!Array.isArray(value)) return null;
|
|
1377
|
+
const out = [];
|
|
1378
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
1379
|
+
if (typeof value[i] === "string") out[out.length] = value[i];
|
|
1380
|
+
}
|
|
1381
|
+
return out;
|
|
1382
|
+
}
|
|
1383
|
+
function takeSnapshot(init) {
|
|
1384
|
+
if (routesReady) return;
|
|
1385
|
+
const raw = init;
|
|
1386
|
+
gatewayPrefixes = copyStrings(raw.gatewayPrefixes);
|
|
1387
|
+
partnerOrigins = copyStrings(raw.partnerOrigins) || [];
|
|
1388
|
+
routesReady = true;
|
|
1389
|
+
}
|
|
1390
|
+
promiseThen(client.ready, takeSnapshot);
|
|
1391
|
+
function matchesPrefix2(pathname, prefix) {
|
|
1392
|
+
const bare = strSlice(prefix, prefix.length - 1) === "/" ? strSlice(prefix, 0, -1) : prefix;
|
|
1393
|
+
return pathname === bare || strSlice(pathname, 0, bare.length + 1) === bare + "/";
|
|
1394
|
+
}
|
|
1395
|
+
function routeOf(origin, protocol, pathname) {
|
|
1396
|
+
if (origin === hostOrigin) {
|
|
1397
|
+
if (gatewayPrefixes === null) return "bridge";
|
|
1398
|
+
for (let i = 0; i < gatewayPrefixes.length; i += 1) {
|
|
1399
|
+
if (matchesPrefix2(pathname, gatewayPrefixes[i])) return "bridge";
|
|
1400
|
+
}
|
|
1401
|
+
return "reject";
|
|
1402
|
+
}
|
|
1403
|
+
for (let i = 0; i < partnerOrigins.length; i += 1) {
|
|
1404
|
+
if (origin === partnerOrigins[i]) return "bridge";
|
|
1405
|
+
}
|
|
1406
|
+
return protocol === "https:" ? "direct" : "reject";
|
|
1407
|
+
}
|
|
1408
|
+
function requestUrlOf(input) {
|
|
1409
|
+
if (!getRequestUrl || typeof input !== "object" || input === null) return void 0;
|
|
1410
|
+
try {
|
|
1411
|
+
return getRequestUrl(input);
|
|
1412
|
+
} catch {
|
|
1413
|
+
return void 0;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
function dispatch2(input, init) {
|
|
1417
|
+
if (typeof NativeURL !== "function" || !getHref || !getOrigin || !getProtocol || !getPathname) {
|
|
1418
|
+
return promiseReject(new TypeErr("bstage fetch: URL is unavailable"));
|
|
1419
|
+
}
|
|
1420
|
+
const requestUrl = requestUrlOf(input);
|
|
1421
|
+
const request = requestUrl !== void 0;
|
|
1422
|
+
let parsed;
|
|
1423
|
+
try {
|
|
1424
|
+
parsed = new NativeURL(request ? requestUrl : String(input), hostOrigin);
|
|
1425
|
+
} catch {
|
|
1426
|
+
return promiseReject(new TypeErr("bstage fetch: invalid url"));
|
|
1427
|
+
}
|
|
1428
|
+
const href = getHref(parsed);
|
|
1429
|
+
const route = routeOf(getOrigin(parsed), getProtocol(parsed), getPathname(parsed));
|
|
1430
|
+
const target = request ? input : href;
|
|
1431
|
+
if (route === "bridge") return bridgeFetch(target, init);
|
|
1432
|
+
if (route === "reject") {
|
|
1433
|
+
return promiseReject(new TypeErr("bstage fetch: destination not allowed"));
|
|
1434
|
+
}
|
|
1435
|
+
if (!nativeFetch) return promiseReject(new TypeErr("bstage fetch: fetch is unavailable"));
|
|
1436
|
+
return nativeFetch(
|
|
1437
|
+
g,
|
|
1438
|
+
target,
|
|
1439
|
+
assign({}, init, { credentials: "omit", referrerPolicy: "no-referrer" })
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
function bstageFetch(input, init) {
|
|
1443
|
+
try {
|
|
1444
|
+
if (routesReady) return dispatch2(input, init);
|
|
1445
|
+
return promiseThen(client.ready, function(value) {
|
|
1446
|
+
takeSnapshot(value);
|
|
1447
|
+
return dispatch2(input, init);
|
|
1448
|
+
});
|
|
1449
|
+
} catch {
|
|
1450
|
+
return promiseReject(new TypeErr("bstage fetch: request failed"));
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
Object.defineProperty(g, "__bstage_fetch__", {
|
|
1454
|
+
value: bstageFetch,
|
|
1455
|
+
writable: false,
|
|
1456
|
+
configurable: false,
|
|
1457
|
+
enumerable: false
|
|
1458
|
+
});
|
|
1459
|
+
return bstageFetch;
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1224
1462
|
// src/srcdoc/escape.ts
|
|
1225
1463
|
var HTML_ESCAPES = {
|
|
1226
1464
|
"&": "&",
|
|
@@ -1286,7 +1524,7 @@ function buildFrameCsp(nonce, assetOrigin, mediaOrigins = []) {
|
|
|
1286
1524
|
`img-src ${media} data: blob:`,
|
|
1287
1525
|
`font-src ${assetOrigin} data:`,
|
|
1288
1526
|
`media-src ${media} blob:`,
|
|
1289
|
-
"connect-src
|
|
1527
|
+
"connect-src https: wss:",
|
|
1290
1528
|
"frame-src 'none'",
|
|
1291
1529
|
"form-action 'none'",
|
|
1292
1530
|
"base-uri 'none'"
|
|
@@ -1303,6 +1541,9 @@ function bootstrapScript(o) {
|
|
|
1303
1541
|
`var bridgeRuntime = ${bridgeRuntime.toString()};`,
|
|
1304
1542
|
`var createBridgeFetch = ${createBridgeFetch.toString()};`,
|
|
1305
1543
|
"var client = bridgeRuntime(globalThis, createBridgeFetch, { session: SESSION, hostOrigin: HOST_ORIGIN });",
|
|
1544
|
+
// 라우터는 항상 심는다. ready.then이 엔트리 로드(아래 client.ready.then)보다 먼저 등록돼야 템플릿보다 먼저 스냅샷을 잡는다.
|
|
1545
|
+
`var installDirectFetch = ${installDirectFetch.toString()};`,
|
|
1546
|
+
"installDirectFetch(globalThis, client, { hostOrigin: HOST_ORIGIN });",
|
|
1306
1547
|
"var lastHeight = -1;",
|
|
1307
1548
|
"var scheduled = false;",
|
|
1308
1549
|
"function reportHeight() {",
|
package/dist/host.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { f as BridgeHostEventType, e as BridgeEventMap,
|
|
2
|
-
export { a as BRIDGE_SURFACES, I as IFRAME_ATTRIBUTES,
|
|
3
|
-
export { C as CreateSrcdocOptions, c as createNonce, d as createSession, e as createSrcdoc, f as isValidSession } from './createSrcdoc-
|
|
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-BjpZyiLy.cjs';
|
|
2
|
+
export { a as BRIDGE_SURFACES, I as IFRAME_ATTRIBUTES, w as isBridgeSurface } from './messages-BjpZyiLy.cjs';
|
|
3
|
+
export { C as CreateSrcdocOptions, c as createNonce, d as createSession, e as createSrcdoc, f as isValidSession } from './createSrcdoc-DgQyvIjG.cjs';
|
|
4
4
|
|
|
5
5
|
/** 호스트 fetch 정책. */
|
|
6
6
|
interface BridgePolicy {
|
|
@@ -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,6 +1,6 @@
|
|
|
1
|
-
import { f as BridgeHostEventType, e as BridgeEventMap,
|
|
2
|
-
export { a as BRIDGE_SURFACES, I as IFRAME_ATTRIBUTES,
|
|
3
|
-
export { C as CreateSrcdocOptions, c as createNonce, d as createSession, e as createSrcdoc, f as isValidSession } from './createSrcdoc-
|
|
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-BjpZyiLy.js';
|
|
2
|
+
export { a as BRIDGE_SURFACES, I as IFRAME_ATTRIBUTES, w as isBridgeSurface } from './messages-BjpZyiLy.js';
|
|
3
|
+
export { C as CreateSrcdocOptions, c as createNonce, d as createSession, e as createSrcdoc, f as isValidSession } from './createSrcdoc-DgQyvIjG.js';
|
|
4
4
|
|
|
5
5
|
/** 호스트 fetch 정책. */
|
|
6
6
|
interface BridgePolicy {
|
|
@@ -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-ND2HFYBP.js";
|
|
36
|
+
import "./chunk-CSAW3L7P.js";
|
|
36
37
|
|
|
37
38
|
// src/host/rateLimiter.ts
|
|
38
39
|
var DEFAULT_WINDOW_MS = 6e4;
|
|
@@ -114,6 +115,7 @@ function buildInit(init, hostOrigin, partnerOrigins, policy) {
|
|
|
114
115
|
spaceId: String(init.spaceId),
|
|
115
116
|
hostOrigin,
|
|
116
117
|
gatewayBase: `${hostOrigin}/gw`,
|
|
118
|
+
gatewayPrefixes: policy.gatewayPrefixes,
|
|
117
119
|
locale: String(init.locale),
|
|
118
120
|
theme: sanitizeTheme(init.theme),
|
|
119
121
|
...slot ? { slot } : {},
|
|
@@ -453,6 +455,9 @@ function assertOptions(options) {
|
|
|
453
455
|
"[bstage-bridge] externalFetchImpl\uC5D0 partnerFetchImpl(\uD32C \uD1A0\uD070 fetch)\uC744 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4"
|
|
454
456
|
);
|
|
455
457
|
}
|
|
458
|
+
if (options.onDispose !== void 0 && typeof options.onDispose !== "function") {
|
|
459
|
+
throw new TypeError("[bstage-bridge] onDispose\uB294 \uD568\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4");
|
|
460
|
+
}
|
|
456
461
|
if (!options.iframe || !options.init) {
|
|
457
462
|
throw new TypeError("[bstage-bridge] iframe\xB7init\uC774 \uD544\uC694\uD569\uB2C8\uB2E4");
|
|
458
463
|
}
|
|
@@ -475,6 +480,8 @@ function createBridgeHost(options) {
|
|
|
475
480
|
let current = buildInit(options.init, hostOrigin, partnerOrigins, policy);
|
|
476
481
|
let inFlightFetches = 0;
|
|
477
482
|
let disposed = false;
|
|
483
|
+
let boundFrame;
|
|
484
|
+
let pendingVisibility;
|
|
478
485
|
let resizeHandle;
|
|
479
486
|
let pendingHeight;
|
|
480
487
|
const raf = typeof win.requestAnimationFrame === "function" ? (cb) => win.requestAnimationFrame(cb) : (cb) => setTimeout(cb, 16);
|
|
@@ -538,13 +545,44 @@ function createBridgeHost(options) {
|
|
|
538
545
|
const env = ev.data;
|
|
539
546
|
if (!isBridgeEnvelope(env)) return;
|
|
540
547
|
if (!hasSession(env.payload, session)) return;
|
|
548
|
+
const token = env.frame;
|
|
549
|
+
if (!isValidSession(token) || boundFrame !== void 0 && token !== boundFrame) {
|
|
550
|
+
dispose("frame-mismatch");
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
boundFrame = token;
|
|
554
|
+
if (env.kind === "evt" && env.type === FRAME_UNLOAD_EVENT) {
|
|
555
|
+
dispose("frame-unload");
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
541
558
|
if (env.kind === "evt" && env.type === READY_EVENT) {
|
|
542
559
|
if (!readyLimiter.take()) return;
|
|
543
560
|
post(frame, createEnvelope("res", INIT_RESPONSE, env.id, { ok: true, data: current }));
|
|
561
|
+
flushPendingVisibility(frame);
|
|
544
562
|
return;
|
|
545
563
|
}
|
|
564
|
+
flushPendingVisibility(frame);
|
|
546
565
|
if (env.kind === "req") void handleRequest(frame, env);
|
|
547
566
|
}
|
|
567
|
+
function flushPendingVisibility(frame) {
|
|
568
|
+
if (pendingVisibility === void 0) return;
|
|
569
|
+
const payload = pendingVisibility;
|
|
570
|
+
pendingVisibility = void 0;
|
|
571
|
+
post(frame, createEnvelope("evt", "visibility", createSession(), payload));
|
|
572
|
+
}
|
|
573
|
+
function dispose(reason) {
|
|
574
|
+
if (disposed) return;
|
|
575
|
+
disposed = true;
|
|
576
|
+
win.removeEventListener("message", onMessage);
|
|
577
|
+
if (resizeHandle !== void 0) cancelRaf(resizeHandle);
|
|
578
|
+
resizeHandle = void 0;
|
|
579
|
+
if (options.onDispose) {
|
|
580
|
+
try {
|
|
581
|
+
options.onDispose(reason);
|
|
582
|
+
} catch {
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
548
586
|
win.addEventListener("message", onMessage);
|
|
549
587
|
return {
|
|
550
588
|
send(type, payload) {
|
|
@@ -555,15 +593,15 @@ function createBridgeHost(options) {
|
|
|
555
593
|
}
|
|
556
594
|
const clean = sanitizeEvent(type, payload);
|
|
557
595
|
current = applyEvent(current, type, clean);
|
|
596
|
+
if (boundFrame === void 0) {
|
|
597
|
+
if (type === "visibility") pendingVisibility = clean;
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
558
600
|
const frame = iframe.contentWindow;
|
|
559
601
|
if (frame) post(frame, createEnvelope("evt", type, createSession(), clean));
|
|
560
602
|
},
|
|
561
603
|
dispose() {
|
|
562
|
-
|
|
563
|
-
disposed = true;
|
|
564
|
-
win.removeEventListener("message", onMessage);
|
|
565
|
-
if (resizeHandle !== void 0) cancelRaf(resizeHandle);
|
|
566
|
-
resizeHandle = void 0;
|
|
604
|
+
dispose("manual");
|
|
567
605
|
}
|
|
568
606
|
};
|
|
569
607
|
}
|