@bstage-sdk/bridge 0.11.0 → 0.13.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 +28 -4
- package/dist/{chunk-ND2HFYBP.js → chunk-NXB47KPE.js} +37 -4
- package/dist/{chunk-CSAW3L7P.js → chunk-TPKNGW34.js} +108 -1
- package/dist/{createSrcdoc-DgQyvIjG.d.cts → createSrcdoc-DYZk6dDJ.d.cts} +13 -2
- package/dist/{createSrcdoc-DgQyvIjG.d.ts → createSrcdoc-DYZk6dDJ.d.ts} +13 -2
- package/dist/frame.cjs +110 -2
- package/dist/frame.d.cts +46 -2
- package/dist/frame.d.ts +46 -2
- package/dist/frame.js +5 -3
- package/dist/host.cjs +181 -3
- package/dist/host.d.cts +12 -4
- package/dist/host.d.ts +12 -4
- package/dist/host.js +55 -3
- package/dist/index.cjs +147 -2
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +16 -2
- package/dist/{messages-BjpZyiLy.d.cts → messages-Cdjpvu2S.d.cts} +29 -2
- package/dist/{messages-BjpZyiLy.d.ts → messages-Cdjpvu2S.d.ts} +29 -2
- package/package.json +1 -1
package/dist/host.cjs
CHANGED
|
@@ -29,6 +29,7 @@ __export(host_exports, {
|
|
|
29
29
|
IFRAME_ATTRIBUTES: () => IFRAME_ATTRIBUTES,
|
|
30
30
|
MAX_DECODE_PASSES: () => MAX_DECODE_PASSES,
|
|
31
31
|
MAX_PARTNER_ORIGINS: () => MAX_PARTNER_ORIGINS,
|
|
32
|
+
REPORTS_PER_SESSION: () => REPORTS_PER_SESSION,
|
|
32
33
|
checkPath: () => checkPath,
|
|
33
34
|
classifyDestination: () => classifyDestination,
|
|
34
35
|
createBridgeHost: () => createBridgeHost,
|
|
@@ -102,7 +103,8 @@ var REQUEST_TYPES = [
|
|
|
102
103
|
"resize",
|
|
103
104
|
"track",
|
|
104
105
|
"storage",
|
|
105
|
-
"log"
|
|
106
|
+
"log",
|
|
107
|
+
"report"
|
|
106
108
|
];
|
|
107
109
|
var HOST_EVENT_TYPES = ["slot.context", "locale", "member", "theme", "visibility"];
|
|
108
110
|
var READY_EVENT = "ready";
|
|
@@ -112,6 +114,12 @@ var BRIDGE_SURFACES = ["page", "slot"];
|
|
|
112
114
|
function isBridgeSurface(value) {
|
|
113
115
|
return BRIDGE_SURFACES.includes(value);
|
|
114
116
|
}
|
|
117
|
+
var TEMPLATE_REPORT_KINDS = ["error", "rejection", "load"];
|
|
118
|
+
var REPORT_MESSAGE_MAX_LENGTH = 300;
|
|
119
|
+
var REPORT_MAX_FRAMES = 10;
|
|
120
|
+
var REPORT_FRAME_MAX_LENGTH = 200;
|
|
121
|
+
var REPORT_COUNT_MAX = 100;
|
|
122
|
+
var REPORTS_PER_SESSION = 5;
|
|
115
123
|
var IFRAME_ATTRIBUTES = {
|
|
116
124
|
sandbox: "allow-scripts",
|
|
117
125
|
allow: "autoplay; fullscreen; picture-in-picture; encrypted-media; clipboard-write",
|
|
@@ -669,6 +677,32 @@ function validateLog(payload) {
|
|
|
669
677
|
if (!known || typeof message !== "string") throw new BridgeError("BAD_REQUEST", "invalid log");
|
|
670
678
|
return { level: known, message: message.slice(0, LOG_MAX_LENGTH) };
|
|
671
679
|
}
|
|
680
|
+
function framesCopy(frames) {
|
|
681
|
+
if (!Array.isArray(frames) || frames.length > REPORT_MAX_FRAMES) return null;
|
|
682
|
+
const out = [];
|
|
683
|
+
for (let i = 0; i < frames.length; i += 1) {
|
|
684
|
+
if (!(i in frames)) return null;
|
|
685
|
+
const f = frames[i];
|
|
686
|
+
if (typeof f !== "string" || f.length > REPORT_FRAME_MAX_LENGTH) return null;
|
|
687
|
+
out.push(f);
|
|
688
|
+
}
|
|
689
|
+
return out;
|
|
690
|
+
}
|
|
691
|
+
function validateReport(payload) {
|
|
692
|
+
const kind = TEMPLATE_REPORT_KINDS.find((k) => k === field(payload, "kind"));
|
|
693
|
+
const message = field(payload, "message");
|
|
694
|
+
const frames = framesCopy(field(payload, "frames"));
|
|
695
|
+
const count = field(payload, "count");
|
|
696
|
+
if (!kind || typeof message !== "string" || message.length > REPORT_MESSAGE_MAX_LENGTH || frames === null || typeof count !== "number" || !Number.isInteger(count) || count < 1 || count > REPORT_COUNT_MAX) {
|
|
697
|
+
throw new BridgeError("BAD_REQUEST", "invalid report");
|
|
698
|
+
}
|
|
699
|
+
return Object.freeze({
|
|
700
|
+
kind,
|
|
701
|
+
message,
|
|
702
|
+
frames: Object.freeze(frames),
|
|
703
|
+
count
|
|
704
|
+
});
|
|
705
|
+
}
|
|
672
706
|
|
|
673
707
|
// src/host/createBridgeHost.ts
|
|
674
708
|
var OPAQUE_ORIGIN = "null";
|
|
@@ -806,6 +840,16 @@ async function dispatch(ctx, type, payload) {
|
|
|
806
840
|
else writeConsole(level, message);
|
|
807
841
|
return EMPTY;
|
|
808
842
|
}
|
|
843
|
+
case "report": {
|
|
844
|
+
const report = validateReport(payload);
|
|
845
|
+
const onReport = ctx.options.onTemplateReport;
|
|
846
|
+
if (!onReport || !ctx.takeReportSlot()) return EMPTY;
|
|
847
|
+
try {
|
|
848
|
+
onReport(report);
|
|
849
|
+
} catch {
|
|
850
|
+
}
|
|
851
|
+
return EMPTY;
|
|
852
|
+
}
|
|
809
853
|
}
|
|
810
854
|
}
|
|
811
855
|
function failure(code, message) {
|
|
@@ -849,6 +893,9 @@ function assertOptions(options) {
|
|
|
849
893
|
if (options.onDispose !== void 0 && typeof options.onDispose !== "function") {
|
|
850
894
|
throw new TypeError("[bstage-bridge] onDispose\uB294 \uD568\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4");
|
|
851
895
|
}
|
|
896
|
+
if (options.onTemplateReport !== void 0 && typeof options.onTemplateReport !== "function") {
|
|
897
|
+
throw new TypeError("[bstage-bridge] onTemplateReport\uB294 \uD568\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4");
|
|
898
|
+
}
|
|
852
899
|
if (!options.iframe || !options.init) {
|
|
853
900
|
throw new TypeError("[bstage-bridge] iframe\xB7init\uC774 \uD544\uC694\uD569\uB2C8\uB2E4");
|
|
854
901
|
}
|
|
@@ -870,6 +917,7 @@ function createBridgeHost(options) {
|
|
|
870
917
|
const resizeLimiter = createRateLimiter(RESIZE_PER_SECOND, 1e3);
|
|
871
918
|
let current = buildInit(options.init, hostOrigin, partnerOrigins, policy);
|
|
872
919
|
let inFlightFetches = 0;
|
|
920
|
+
let reportsTaken = 0;
|
|
873
921
|
let disposed = false;
|
|
874
922
|
let boundFrame;
|
|
875
923
|
let pendingVisibility;
|
|
@@ -899,7 +947,12 @@ function createBridgeHost(options) {
|
|
|
899
947
|
externalFetchImpl: options.externalFetchImpl ?? defaultUncredentialedFetch,
|
|
900
948
|
hostOrigin,
|
|
901
949
|
toastLimiter: createRateLimiter(TOAST_PER_MINUTE),
|
|
902
|
-
scheduleResize
|
|
950
|
+
scheduleResize,
|
|
951
|
+
takeReportSlot() {
|
|
952
|
+
if (reportsTaken >= REPORTS_PER_SESSION) return false;
|
|
953
|
+
reportsTaken += 1;
|
|
954
|
+
return true;
|
|
955
|
+
}
|
|
903
956
|
};
|
|
904
957
|
function post(target, envelope) {
|
|
905
958
|
if (disposed) return;
|
|
@@ -1459,6 +1512,112 @@ function installDirectFetch(g, client, config) {
|
|
|
1459
1512
|
return bstageFetch;
|
|
1460
1513
|
}
|
|
1461
1514
|
|
|
1515
|
+
// src/frame/errorReport.ts
|
|
1516
|
+
function installErrorReport(g, client, config) {
|
|
1517
|
+
const schedule = typeof g.setTimeout === "function" ? g.setTimeout : void 0;
|
|
1518
|
+
const entries = /* @__PURE__ */ new Map();
|
|
1519
|
+
let admitted = 0;
|
|
1520
|
+
function toText(value) {
|
|
1521
|
+
if (typeof value === "string") return value;
|
|
1522
|
+
try {
|
|
1523
|
+
return String(value);
|
|
1524
|
+
} catch {
|
|
1525
|
+
return "";
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
function framesOf(stack) {
|
|
1529
|
+
const out = [];
|
|
1530
|
+
if (typeof stack !== "string") return out;
|
|
1531
|
+
const lines = stack.split("\n");
|
|
1532
|
+
for (let i = 0; i < lines.length && out.length < config.maxFrames; i += 1) {
|
|
1533
|
+
const line = lines[i].trim();
|
|
1534
|
+
if (/^at\s/.test(line) || /^[^@]*@(?:[a-z][\w+.-]*:\/\/|\/)\S*:\d+:\d+$/i.test(line) || /^(?:[a-z][\w+.-]*:\/\/|\/)\S*:\d+:\d+$/i.test(line))
|
|
1535
|
+
out.push(line.slice(0, config.maxFrameLength));
|
|
1536
|
+
}
|
|
1537
|
+
return out;
|
|
1538
|
+
}
|
|
1539
|
+
function send(key) {
|
|
1540
|
+
const entry = entries.get(key);
|
|
1541
|
+
if (!entry || entry.sent) return;
|
|
1542
|
+
entries.set(key, {
|
|
1543
|
+
kind: entry.kind,
|
|
1544
|
+
message: entry.message,
|
|
1545
|
+
frames: entry.frames,
|
|
1546
|
+
count: entry.count,
|
|
1547
|
+
sent: true
|
|
1548
|
+
});
|
|
1549
|
+
try {
|
|
1550
|
+
client.request("report", {
|
|
1551
|
+
kind: entry.kind,
|
|
1552
|
+
message: entry.message,
|
|
1553
|
+
frames: entry.frames,
|
|
1554
|
+
count: entry.count
|
|
1555
|
+
}).catch(function() {
|
|
1556
|
+
});
|
|
1557
|
+
} catch {
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
function record(kind, message, stack) {
|
|
1561
|
+
try {
|
|
1562
|
+
const text = toText(message).slice(0, config.maxMessageLength);
|
|
1563
|
+
const frames = framesOf(stack);
|
|
1564
|
+
const key = kind + "|" + text.replace(/\d+/g, "") + "|" + (frames[0] || "");
|
|
1565
|
+
const entry = entries.get(key);
|
|
1566
|
+
if (entry) {
|
|
1567
|
+
if (!entry.sent && entry.count < config.maxCount) {
|
|
1568
|
+
entries.set(key, {
|
|
1569
|
+
kind: entry.kind,
|
|
1570
|
+
message: entry.message,
|
|
1571
|
+
frames: entry.frames,
|
|
1572
|
+
count: entry.count + 1,
|
|
1573
|
+
sent: false
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
if (admitted >= config.maxReports) return;
|
|
1579
|
+
admitted += 1;
|
|
1580
|
+
entries.set(key, { kind, message: text, frames, count: 1, sent: false });
|
|
1581
|
+
if (admitted >= config.maxReports) stop();
|
|
1582
|
+
if (schedule) {
|
|
1583
|
+
schedule(function() {
|
|
1584
|
+
send(key);
|
|
1585
|
+
}, config.flushDelayMs);
|
|
1586
|
+
} else {
|
|
1587
|
+
send(key);
|
|
1588
|
+
}
|
|
1589
|
+
} catch {
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
function onError(ev) {
|
|
1593
|
+
const err = ev && ev.error;
|
|
1594
|
+
if (err && typeof err === "object") {
|
|
1595
|
+
record("error", typeof err.message === "string" ? err.message : ev.message, err.stack);
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
const where = ev && typeof ev.filename === "string" && ev.filename !== "" ? "at " + ev.filename + ":" + toText(ev.lineno) + ":" + toText(ev.colno) : "";
|
|
1599
|
+
record("error", ev ? ev.message : "", where);
|
|
1600
|
+
}
|
|
1601
|
+
function onRejection(ev) {
|
|
1602
|
+
const reason = ev ? ev.reason : void 0;
|
|
1603
|
+
if (reason && typeof reason === "object" && typeof reason.message === "string") {
|
|
1604
|
+
record("rejection", reason.message, reason.stack);
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
record("rejection", reason, "");
|
|
1608
|
+
}
|
|
1609
|
+
function stop() {
|
|
1610
|
+
if (typeof g.removeEventListener !== "function") return;
|
|
1611
|
+
g.removeEventListener("error", onError);
|
|
1612
|
+
g.removeEventListener("unhandledrejection", onRejection);
|
|
1613
|
+
}
|
|
1614
|
+
if (typeof g.addEventListener === "function") {
|
|
1615
|
+
g.addEventListener("error", onError);
|
|
1616
|
+
g.addEventListener("unhandledrejection", onRejection);
|
|
1617
|
+
}
|
|
1618
|
+
return record;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1462
1621
|
// src/srcdoc/escape.ts
|
|
1463
1622
|
var HTML_ESCAPES = {
|
|
1464
1623
|
"&": "&",
|
|
@@ -1476,6 +1635,7 @@ function toScriptLiteral(value) {
|
|
|
1476
1635
|
|
|
1477
1636
|
// src/srcdoc/createSrcdoc.ts
|
|
1478
1637
|
var MAX_MEDIA_ORIGINS = 20;
|
|
1638
|
+
var REPORT_FLUSH_DELAY_MS = 1e3;
|
|
1479
1639
|
var MAX_ELEMENT_NAME_LENGTH = 100;
|
|
1480
1640
|
var ELEMENT_NAME_RE = /^[a-z][a-z0-9._]*-[a-z0-9._-]*$/;
|
|
1481
1641
|
function isValidElementName(value) {
|
|
@@ -1544,6 +1704,16 @@ function bootstrapScript(o) {
|
|
|
1544
1704
|
// 라우터는 항상 심는다. ready.then이 엔트리 로드(아래 client.ready.then)보다 먼저 등록돼야 템플릿보다 먼저 스냅샷을 잡는다.
|
|
1545
1705
|
`var installDirectFetch = ${installDirectFetch.toString()};`,
|
|
1546
1706
|
"installDirectFetch(globalThis, client, { hostOrigin: HOST_ORIGIN });",
|
|
1707
|
+
// 에러 수집기는 엔트리(템플릿)보다 먼저 리스너를 건다. 상한은 호스트 검증기와 같은 프로토콜 값이다.
|
|
1708
|
+
`var installErrorReport = ${installErrorReport.toString()};`,
|
|
1709
|
+
"var reportError = installErrorReport(globalThis, client, {",
|
|
1710
|
+
` maxReports: ${REPORTS_PER_SESSION},`,
|
|
1711
|
+
` maxMessageLength: ${REPORT_MESSAGE_MAX_LENGTH},`,
|
|
1712
|
+
` maxFrames: ${REPORT_MAX_FRAMES},`,
|
|
1713
|
+
` maxFrameLength: ${REPORT_FRAME_MAX_LENGTH},`,
|
|
1714
|
+
` maxCount: ${REPORT_COUNT_MAX},`,
|
|
1715
|
+
` flushDelayMs: ${REPORT_FLUSH_DELAY_MS}`,
|
|
1716
|
+
"});",
|
|
1547
1717
|
"var lastHeight = -1;",
|
|
1548
1718
|
"var scheduled = false;",
|
|
1549
1719
|
"function reportHeight() {",
|
|
@@ -1560,9 +1730,12 @@ function bootstrapScript(o) {
|
|
|
1560
1730
|
"}",
|
|
1561
1731
|
"client.ready.then(function () {",
|
|
1562
1732
|
" var script = document.createElement('script');",
|
|
1733
|
+
// crossOrigin은 src보다 먼저 둔다(요청 모드가 src 대입 시점에 정해지는 브라우저가 있다). 주지 않으면 줄 자체가 없다.
|
|
1734
|
+
...o.crossOrigin === "anonymous" ? [" script.crossOrigin = 'anonymous';"] : [],
|
|
1563
1735
|
" script.src = ENTRY_URL;",
|
|
1564
1736
|
" script.onerror = function () {",
|
|
1565
1737
|
" client.request('log', { level: 'error', message: 'template entry failed to load' }).catch(function () {});",
|
|
1738
|
+
" reportError('load', 'template entry failed to load', '');",
|
|
1566
1739
|
" };",
|
|
1567
1740
|
" document.head.appendChild(script);",
|
|
1568
1741
|
" customElements.whenDefined(ELEMENT_NAME).then(function () {",
|
|
@@ -1588,8 +1761,12 @@ function createSrcdoc(options) {
|
|
|
1588
1761
|
}
|
|
1589
1762
|
if (typeof title !== "string") throw new TypeError("[bstage-bridge] title\uC740 \uBB38\uC790\uC5F4\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4");
|
|
1590
1763
|
const mediaOrigins = normalizeMediaOrigins(options.mediaOrigins);
|
|
1764
|
+
const { crossOrigin } = options;
|
|
1765
|
+
if (crossOrigin !== void 0 && crossOrigin !== "anonymous") {
|
|
1766
|
+
throw new TypeError("[bstage-bridge] crossOrigin\uC740 'anonymous'\uB9CC \uBC1B\uC2B5\uB2C8\uB2E4");
|
|
1767
|
+
}
|
|
1591
1768
|
const csp = buildFrameCsp(nonce, assetOrigin, mediaOrigins);
|
|
1592
|
-
const script = bootstrapScript({ session, hostOrigin, entryUrl, elementName });
|
|
1769
|
+
const script = bootstrapScript({ session, hostOrigin, entryUrl, elementName, crossOrigin });
|
|
1593
1770
|
if (/<\/script|<!--/i.test(script)) {
|
|
1594
1771
|
throw new Error("[bstage-bridge] \uBD80\uD2B8\uC2A4\uD2B8\uB7A9 \uC2A4\uD06C\uB9BD\uD2B8\uC5D0 \uC2A4\uD06C\uB9BD\uD2B8 \uC885\uB8CC \uC2DC\uD000\uC2A4\uAC00 \uC788\uC2B5\uB2C8\uB2E4");
|
|
1595
1772
|
}
|
|
@@ -1623,6 +1800,7 @@ ${script}
|
|
|
1623
1800
|
IFRAME_ATTRIBUTES,
|
|
1624
1801
|
MAX_DECODE_PASSES,
|
|
1625
1802
|
MAX_PARTNER_ORIGINS,
|
|
1803
|
+
REPORTS_PER_SESSION,
|
|
1626
1804
|
checkPath,
|
|
1627
1805
|
classifyDestination,
|
|
1628
1806
|
createBridgeHost,
|
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, C as ToastKind, L as LogLevel, l as BridgeSurface, A as ThemeState, S as SlotState, M as MemberState, m as BridgeTranslations, y as TemplateReport } from './messages-Cdjpvu2S.cjs';
|
|
2
|
+
export { a as BRIDGE_SURFACES, I as IFRAME_ATTRIBUTES, q as REPORTS_PER_SESSION, z as TemplateReportKind, G as isBridgeSurface } from './messages-Cdjpvu2S.cjs';
|
|
3
|
+
export { C as CreateSrcdocOptions, c as createNonce, d as createSession, e as createSrcdoc, f as isValidSession } from './createSrcdoc-DYZk6dDJ.cjs';
|
|
4
4
|
|
|
5
5
|
/** 호스트 fetch 정책. */
|
|
6
6
|
interface BridgePolicy {
|
|
@@ -219,6 +219,14 @@ interface CreateBridgeHostOptions {
|
|
|
219
219
|
* 바로 걷어 내는 데 쓴다. 이 콜백의 예외는 삼킨다.
|
|
220
220
|
*/
|
|
221
221
|
readonly onDispose?: (reason: BridgeDisposeReason) => void;
|
|
222
|
+
/**
|
|
223
|
+
* 프레임의 `report`(템플릿 에러 보고)를 검증한 뒤 부른다. phase와 무관하게(real 포함) 불린다.
|
|
224
|
+
* 프레임 세션(이 호스트)당 `REPORTS_PER_SESSION`(5)건까지만 부르고 넘치는 보고는 조용히 버린다.
|
|
225
|
+
* 넘기는 값은 `{ kind, message, frames, count }`뿐이다 — 어느 템플릿인지는 프레임이 보낸 값이 아니라
|
|
226
|
+
* 호스트가 띄운 templateUrl로 임베더가 귀속한다. message·frames는 템플릿이 만든 문자열이라 텍스트로만 다루고,
|
|
227
|
+
* 밖으로 보내기 전 스크러빙은 임베더 몫이다. 이 콜백의 예외는 삼킨다.
|
|
228
|
+
*/
|
|
229
|
+
readonly onTemplateReport?: (report: TemplateReport) => void;
|
|
222
230
|
}
|
|
223
231
|
/** `onDispose` 사유. */
|
|
224
232
|
type BridgeDisposeReason = 'frame-unload' | 'frame-mismatch' | 'manual';
|
|
@@ -277,4 +285,4 @@ interface PathCheck {
|
|
|
277
285
|
*/
|
|
278
286
|
declare function checkPath(pathname: string, options?: PathCheckOptions): PathCheck;
|
|
279
287
|
|
|
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 };
|
|
288
|
+
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, TemplateReport, 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, C as ToastKind, L as LogLevel, l as BridgeSurface, A as ThemeState, S as SlotState, M as MemberState, m as BridgeTranslations, y as TemplateReport } from './messages-Cdjpvu2S.js';
|
|
2
|
+
export { a as BRIDGE_SURFACES, I as IFRAME_ATTRIBUTES, q as REPORTS_PER_SESSION, z as TemplateReportKind, G as isBridgeSurface } from './messages-Cdjpvu2S.js';
|
|
3
|
+
export { C as CreateSrcdocOptions, c as createNonce, d as createSession, e as createSrcdoc, f as isValidSession } from './createSrcdoc-DYZk6dDJ.js';
|
|
4
4
|
|
|
5
5
|
/** 호스트 fetch 정책. */
|
|
6
6
|
interface BridgePolicy {
|
|
@@ -219,6 +219,14 @@ interface CreateBridgeHostOptions {
|
|
|
219
219
|
* 바로 걷어 내는 데 쓴다. 이 콜백의 예외는 삼킨다.
|
|
220
220
|
*/
|
|
221
221
|
readonly onDispose?: (reason: BridgeDisposeReason) => void;
|
|
222
|
+
/**
|
|
223
|
+
* 프레임의 `report`(템플릿 에러 보고)를 검증한 뒤 부른다. phase와 무관하게(real 포함) 불린다.
|
|
224
|
+
* 프레임 세션(이 호스트)당 `REPORTS_PER_SESSION`(5)건까지만 부르고 넘치는 보고는 조용히 버린다.
|
|
225
|
+
* 넘기는 값은 `{ kind, message, frames, count }`뿐이다 — 어느 템플릿인지는 프레임이 보낸 값이 아니라
|
|
226
|
+
* 호스트가 띄운 templateUrl로 임베더가 귀속한다. message·frames는 템플릿이 만든 문자열이라 텍스트로만 다루고,
|
|
227
|
+
* 밖으로 보내기 전 스크러빙은 임베더 몫이다. 이 콜백의 예외는 삼킨다.
|
|
228
|
+
*/
|
|
229
|
+
readonly onTemplateReport?: (report: TemplateReport) => void;
|
|
222
230
|
}
|
|
223
231
|
/** `onDispose` 사유. */
|
|
224
232
|
type BridgeDisposeReason = 'frame-unload' | 'frame-mismatch' | 'manual';
|
|
@@ -277,4 +285,4 @@ interface PathCheck {
|
|
|
277
285
|
*/
|
|
278
286
|
declare function checkPath(pathname: string, options?: PathCheckOptions): PathCheck;
|
|
279
287
|
|
|
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 };
|
|
288
|
+
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, TemplateReport, checkPath, classifyDestination, createBridgeHost, isAllowedGatewayPath, normalizePartnerOrigins, normalizePinnedHeaders, prepareFetch, resolveDestination, resolvePolicy };
|
package/dist/host.js
CHANGED
|
@@ -12,6 +12,12 @@ import {
|
|
|
12
12
|
MAX_DECODE_PASSES,
|
|
13
13
|
MAX_PARTNER_ORIGINS,
|
|
14
14
|
READY_EVENT,
|
|
15
|
+
REPORTS_PER_SESSION,
|
|
16
|
+
REPORT_COUNT_MAX,
|
|
17
|
+
REPORT_FRAME_MAX_LENGTH,
|
|
18
|
+
REPORT_MAX_FRAMES,
|
|
19
|
+
REPORT_MESSAGE_MAX_LENGTH,
|
|
20
|
+
TEMPLATE_REPORT_KINDS,
|
|
15
21
|
checkPath,
|
|
16
22
|
classifyDestination,
|
|
17
23
|
createEnvelope,
|
|
@@ -32,8 +38,8 @@ import {
|
|
|
32
38
|
prepareFetch,
|
|
33
39
|
resolveDestination,
|
|
34
40
|
resolvePolicy
|
|
35
|
-
} from "./chunk-
|
|
36
|
-
import "./chunk-
|
|
41
|
+
} from "./chunk-NXB47KPE.js";
|
|
42
|
+
import "./chunk-TPKNGW34.js";
|
|
37
43
|
|
|
38
44
|
// src/host/rateLimiter.ts
|
|
39
45
|
var DEFAULT_WINDOW_MS = 6e4;
|
|
@@ -278,6 +284,32 @@ function validateLog(payload) {
|
|
|
278
284
|
if (!known || typeof message !== "string") throw new BridgeError("BAD_REQUEST", "invalid log");
|
|
279
285
|
return { level: known, message: message.slice(0, LOG_MAX_LENGTH) };
|
|
280
286
|
}
|
|
287
|
+
function framesCopy(frames) {
|
|
288
|
+
if (!Array.isArray(frames) || frames.length > REPORT_MAX_FRAMES) return null;
|
|
289
|
+
const out = [];
|
|
290
|
+
for (let i = 0; i < frames.length; i += 1) {
|
|
291
|
+
if (!(i in frames)) return null;
|
|
292
|
+
const f = frames[i];
|
|
293
|
+
if (typeof f !== "string" || f.length > REPORT_FRAME_MAX_LENGTH) return null;
|
|
294
|
+
out.push(f);
|
|
295
|
+
}
|
|
296
|
+
return out;
|
|
297
|
+
}
|
|
298
|
+
function validateReport(payload) {
|
|
299
|
+
const kind = TEMPLATE_REPORT_KINDS.find((k) => k === field(payload, "kind"));
|
|
300
|
+
const message = field(payload, "message");
|
|
301
|
+
const frames = framesCopy(field(payload, "frames"));
|
|
302
|
+
const count = field(payload, "count");
|
|
303
|
+
if (!kind || typeof message !== "string" || message.length > REPORT_MESSAGE_MAX_LENGTH || frames === null || typeof count !== "number" || !Number.isInteger(count) || count < 1 || count > REPORT_COUNT_MAX) {
|
|
304
|
+
throw new BridgeError("BAD_REQUEST", "invalid report");
|
|
305
|
+
}
|
|
306
|
+
return Object.freeze({
|
|
307
|
+
kind,
|
|
308
|
+
message,
|
|
309
|
+
frames: Object.freeze(frames),
|
|
310
|
+
count
|
|
311
|
+
});
|
|
312
|
+
}
|
|
281
313
|
|
|
282
314
|
// src/host/createBridgeHost.ts
|
|
283
315
|
var OPAQUE_ORIGIN = "null";
|
|
@@ -415,6 +447,16 @@ async function dispatch(ctx, type, payload) {
|
|
|
415
447
|
else writeConsole(level, message);
|
|
416
448
|
return EMPTY;
|
|
417
449
|
}
|
|
450
|
+
case "report": {
|
|
451
|
+
const report = validateReport(payload);
|
|
452
|
+
const onReport = ctx.options.onTemplateReport;
|
|
453
|
+
if (!onReport || !ctx.takeReportSlot()) return EMPTY;
|
|
454
|
+
try {
|
|
455
|
+
onReport(report);
|
|
456
|
+
} catch {
|
|
457
|
+
}
|
|
458
|
+
return EMPTY;
|
|
459
|
+
}
|
|
418
460
|
}
|
|
419
461
|
}
|
|
420
462
|
function failure(code, message) {
|
|
@@ -458,6 +500,9 @@ function assertOptions(options) {
|
|
|
458
500
|
if (options.onDispose !== void 0 && typeof options.onDispose !== "function") {
|
|
459
501
|
throw new TypeError("[bstage-bridge] onDispose\uB294 \uD568\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4");
|
|
460
502
|
}
|
|
503
|
+
if (options.onTemplateReport !== void 0 && typeof options.onTemplateReport !== "function") {
|
|
504
|
+
throw new TypeError("[bstage-bridge] onTemplateReport\uB294 \uD568\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4");
|
|
505
|
+
}
|
|
461
506
|
if (!options.iframe || !options.init) {
|
|
462
507
|
throw new TypeError("[bstage-bridge] iframe\xB7init\uC774 \uD544\uC694\uD569\uB2C8\uB2E4");
|
|
463
508
|
}
|
|
@@ -479,6 +524,7 @@ function createBridgeHost(options) {
|
|
|
479
524
|
const resizeLimiter = createRateLimiter(RESIZE_PER_SECOND, 1e3);
|
|
480
525
|
let current = buildInit(options.init, hostOrigin, partnerOrigins, policy);
|
|
481
526
|
let inFlightFetches = 0;
|
|
527
|
+
let reportsTaken = 0;
|
|
482
528
|
let disposed = false;
|
|
483
529
|
let boundFrame;
|
|
484
530
|
let pendingVisibility;
|
|
@@ -508,7 +554,12 @@ function createBridgeHost(options) {
|
|
|
508
554
|
externalFetchImpl: options.externalFetchImpl ?? defaultUncredentialedFetch,
|
|
509
555
|
hostOrigin,
|
|
510
556
|
toastLimiter: createRateLimiter(TOAST_PER_MINUTE),
|
|
511
|
-
scheduleResize
|
|
557
|
+
scheduleResize,
|
|
558
|
+
takeReportSlot() {
|
|
559
|
+
if (reportsTaken >= REPORTS_PER_SESSION) return false;
|
|
560
|
+
reportsTaken += 1;
|
|
561
|
+
return true;
|
|
562
|
+
}
|
|
512
563
|
};
|
|
513
564
|
function post(target, envelope) {
|
|
514
565
|
if (disposed) return;
|
|
@@ -615,6 +666,7 @@ export {
|
|
|
615
666
|
IFRAME_ATTRIBUTES,
|
|
616
667
|
MAX_DECODE_PASSES,
|
|
617
668
|
MAX_PARTNER_ORIGINS,
|
|
669
|
+
REPORTS_PER_SESSION,
|
|
618
670
|
checkPath,
|
|
619
671
|
classifyDestination,
|
|
620
672
|
createBridgeHost,
|
package/dist/index.cjs
CHANGED
|
@@ -34,8 +34,15 @@ __export(src_exports, {
|
|
|
34
34
|
MAX_ENVELOPE_TYPE_LENGTH: () => MAX_ENVELOPE_TYPE_LENGTH,
|
|
35
35
|
MAX_MEDIA_ORIGINS: () => MAX_MEDIA_ORIGINS,
|
|
36
36
|
READY_EVENT: () => READY_EVENT,
|
|
37
|
+
REPORTS_PER_SESSION: () => REPORTS_PER_SESSION,
|
|
38
|
+
REPORT_COUNT_MAX: () => REPORT_COUNT_MAX,
|
|
39
|
+
REPORT_FLUSH_DELAY_MS: () => REPORT_FLUSH_DELAY_MS,
|
|
40
|
+
REPORT_FRAME_MAX_LENGTH: () => REPORT_FRAME_MAX_LENGTH,
|
|
41
|
+
REPORT_MAX_FRAMES: () => REPORT_MAX_FRAMES,
|
|
42
|
+
REPORT_MESSAGE_MAX_LENGTH: () => REPORT_MESSAGE_MAX_LENGTH,
|
|
37
43
|
REQUEST_TIMEOUT_MS: () => REQUEST_TIMEOUT_MS,
|
|
38
44
|
REQUEST_TYPES: () => REQUEST_TYPES,
|
|
45
|
+
TEMPLATE_REPORT_KINDS: () => TEMPLATE_REPORT_KINDS,
|
|
39
46
|
buildFrameCsp: () => buildFrameCsp,
|
|
40
47
|
createEnvelope: () => createEnvelope,
|
|
41
48
|
createNonce: () => createNonce,
|
|
@@ -110,7 +117,8 @@ var REQUEST_TYPES = [
|
|
|
110
117
|
"resize",
|
|
111
118
|
"track",
|
|
112
119
|
"storage",
|
|
113
|
-
"log"
|
|
120
|
+
"log",
|
|
121
|
+
"report"
|
|
114
122
|
];
|
|
115
123
|
var HOST_EVENT_TYPES = ["slot.context", "locale", "member", "theme", "visibility"];
|
|
116
124
|
var READY_EVENT = "ready";
|
|
@@ -120,6 +128,12 @@ var BRIDGE_SURFACES = ["page", "slot"];
|
|
|
120
128
|
function isBridgeSurface(value) {
|
|
121
129
|
return BRIDGE_SURFACES.includes(value);
|
|
122
130
|
}
|
|
131
|
+
var TEMPLATE_REPORT_KINDS = ["error", "rejection", "load"];
|
|
132
|
+
var REPORT_MESSAGE_MAX_LENGTH = 300;
|
|
133
|
+
var REPORT_MAX_FRAMES = 10;
|
|
134
|
+
var REPORT_FRAME_MAX_LENGTH = 200;
|
|
135
|
+
var REPORT_COUNT_MAX = 100;
|
|
136
|
+
var REPORTS_PER_SESSION = 5;
|
|
123
137
|
var IFRAME_ATTRIBUTES = {
|
|
124
138
|
sandbox: "allow-scripts",
|
|
125
139
|
allow: "autoplay; fullscreen; picture-in-picture; encrypted-media; clipboard-write",
|
|
@@ -616,6 +630,112 @@ function installDirectFetch(g, client, config) {
|
|
|
616
630
|
return bstageFetch;
|
|
617
631
|
}
|
|
618
632
|
|
|
633
|
+
// src/frame/errorReport.ts
|
|
634
|
+
function installErrorReport(g, client, config) {
|
|
635
|
+
const schedule = typeof g.setTimeout === "function" ? g.setTimeout : void 0;
|
|
636
|
+
const entries = /* @__PURE__ */ new Map();
|
|
637
|
+
let admitted = 0;
|
|
638
|
+
function toText(value) {
|
|
639
|
+
if (typeof value === "string") return value;
|
|
640
|
+
try {
|
|
641
|
+
return String(value);
|
|
642
|
+
} catch {
|
|
643
|
+
return "";
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
function framesOf(stack) {
|
|
647
|
+
const out = [];
|
|
648
|
+
if (typeof stack !== "string") return out;
|
|
649
|
+
const lines = stack.split("\n");
|
|
650
|
+
for (let i = 0; i < lines.length && out.length < config.maxFrames; i += 1) {
|
|
651
|
+
const line = lines[i].trim();
|
|
652
|
+
if (/^at\s/.test(line) || /^[^@]*@(?:[a-z][\w+.-]*:\/\/|\/)\S*:\d+:\d+$/i.test(line) || /^(?:[a-z][\w+.-]*:\/\/|\/)\S*:\d+:\d+$/i.test(line))
|
|
653
|
+
out.push(line.slice(0, config.maxFrameLength));
|
|
654
|
+
}
|
|
655
|
+
return out;
|
|
656
|
+
}
|
|
657
|
+
function send(key) {
|
|
658
|
+
const entry = entries.get(key);
|
|
659
|
+
if (!entry || entry.sent) return;
|
|
660
|
+
entries.set(key, {
|
|
661
|
+
kind: entry.kind,
|
|
662
|
+
message: entry.message,
|
|
663
|
+
frames: entry.frames,
|
|
664
|
+
count: entry.count,
|
|
665
|
+
sent: true
|
|
666
|
+
});
|
|
667
|
+
try {
|
|
668
|
+
client.request("report", {
|
|
669
|
+
kind: entry.kind,
|
|
670
|
+
message: entry.message,
|
|
671
|
+
frames: entry.frames,
|
|
672
|
+
count: entry.count
|
|
673
|
+
}).catch(function() {
|
|
674
|
+
});
|
|
675
|
+
} catch {
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
function record(kind, message, stack) {
|
|
679
|
+
try {
|
|
680
|
+
const text = toText(message).slice(0, config.maxMessageLength);
|
|
681
|
+
const frames = framesOf(stack);
|
|
682
|
+
const key = kind + "|" + text.replace(/\d+/g, "") + "|" + (frames[0] || "");
|
|
683
|
+
const entry = entries.get(key);
|
|
684
|
+
if (entry) {
|
|
685
|
+
if (!entry.sent && entry.count < config.maxCount) {
|
|
686
|
+
entries.set(key, {
|
|
687
|
+
kind: entry.kind,
|
|
688
|
+
message: entry.message,
|
|
689
|
+
frames: entry.frames,
|
|
690
|
+
count: entry.count + 1,
|
|
691
|
+
sent: false
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
if (admitted >= config.maxReports) return;
|
|
697
|
+
admitted += 1;
|
|
698
|
+
entries.set(key, { kind, message: text, frames, count: 1, sent: false });
|
|
699
|
+
if (admitted >= config.maxReports) stop();
|
|
700
|
+
if (schedule) {
|
|
701
|
+
schedule(function() {
|
|
702
|
+
send(key);
|
|
703
|
+
}, config.flushDelayMs);
|
|
704
|
+
} else {
|
|
705
|
+
send(key);
|
|
706
|
+
}
|
|
707
|
+
} catch {
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
function onError(ev) {
|
|
711
|
+
const err = ev && ev.error;
|
|
712
|
+
if (err && typeof err === "object") {
|
|
713
|
+
record("error", typeof err.message === "string" ? err.message : ev.message, err.stack);
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
const where = ev && typeof ev.filename === "string" && ev.filename !== "" ? "at " + ev.filename + ":" + toText(ev.lineno) + ":" + toText(ev.colno) : "";
|
|
717
|
+
record("error", ev ? ev.message : "", where);
|
|
718
|
+
}
|
|
719
|
+
function onRejection(ev) {
|
|
720
|
+
const reason = ev ? ev.reason : void 0;
|
|
721
|
+
if (reason && typeof reason === "object" && typeof reason.message === "string") {
|
|
722
|
+
record("rejection", reason.message, reason.stack);
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
record("rejection", reason, "");
|
|
726
|
+
}
|
|
727
|
+
function stop() {
|
|
728
|
+
if (typeof g.removeEventListener !== "function") return;
|
|
729
|
+
g.removeEventListener("error", onError);
|
|
730
|
+
g.removeEventListener("unhandledrejection", onRejection);
|
|
731
|
+
}
|
|
732
|
+
if (typeof g.addEventListener === "function") {
|
|
733
|
+
g.addEventListener("error", onError);
|
|
734
|
+
g.addEventListener("unhandledrejection", onRejection);
|
|
735
|
+
}
|
|
736
|
+
return record;
|
|
737
|
+
}
|
|
738
|
+
|
|
619
739
|
// src/srcdoc/escape.ts
|
|
620
740
|
var HTML_ESCAPES = {
|
|
621
741
|
"&": "&",
|
|
@@ -708,6 +828,7 @@ var LOCAL_HOST_NAMES = Object.freeze([
|
|
|
708
828
|
|
|
709
829
|
// src/srcdoc/createSrcdoc.ts
|
|
710
830
|
var MAX_MEDIA_ORIGINS = 20;
|
|
831
|
+
var REPORT_FLUSH_DELAY_MS = 1e3;
|
|
711
832
|
var MAX_ELEMENT_NAME_LENGTH = 100;
|
|
712
833
|
var ELEMENT_NAME_RE = /^[a-z][a-z0-9._]*-[a-z0-9._-]*$/;
|
|
713
834
|
function isValidElementName(value) {
|
|
@@ -776,6 +897,16 @@ function bootstrapScript(o) {
|
|
|
776
897
|
// 라우터는 항상 심는다. ready.then이 엔트리 로드(아래 client.ready.then)보다 먼저 등록돼야 템플릿보다 먼저 스냅샷을 잡는다.
|
|
777
898
|
`var installDirectFetch = ${installDirectFetch.toString()};`,
|
|
778
899
|
"installDirectFetch(globalThis, client, { hostOrigin: HOST_ORIGIN });",
|
|
900
|
+
// 에러 수집기는 엔트리(템플릿)보다 먼저 리스너를 건다. 상한은 호스트 검증기와 같은 프로토콜 값이다.
|
|
901
|
+
`var installErrorReport = ${installErrorReport.toString()};`,
|
|
902
|
+
"var reportError = installErrorReport(globalThis, client, {",
|
|
903
|
+
` maxReports: ${REPORTS_PER_SESSION},`,
|
|
904
|
+
` maxMessageLength: ${REPORT_MESSAGE_MAX_LENGTH},`,
|
|
905
|
+
` maxFrames: ${REPORT_MAX_FRAMES},`,
|
|
906
|
+
` maxFrameLength: ${REPORT_FRAME_MAX_LENGTH},`,
|
|
907
|
+
` maxCount: ${REPORT_COUNT_MAX},`,
|
|
908
|
+
` flushDelayMs: ${REPORT_FLUSH_DELAY_MS}`,
|
|
909
|
+
"});",
|
|
779
910
|
"var lastHeight = -1;",
|
|
780
911
|
"var scheduled = false;",
|
|
781
912
|
"function reportHeight() {",
|
|
@@ -792,9 +923,12 @@ function bootstrapScript(o) {
|
|
|
792
923
|
"}",
|
|
793
924
|
"client.ready.then(function () {",
|
|
794
925
|
" var script = document.createElement('script');",
|
|
926
|
+
// crossOrigin은 src보다 먼저 둔다(요청 모드가 src 대입 시점에 정해지는 브라우저가 있다). 주지 않으면 줄 자체가 없다.
|
|
927
|
+
...o.crossOrigin === "anonymous" ? [" script.crossOrigin = 'anonymous';"] : [],
|
|
795
928
|
" script.src = ENTRY_URL;",
|
|
796
929
|
" script.onerror = function () {",
|
|
797
930
|
" client.request('log', { level: 'error', message: 'template entry failed to load' }).catch(function () {});",
|
|
931
|
+
" reportError('load', 'template entry failed to load', '');",
|
|
798
932
|
" };",
|
|
799
933
|
" document.head.appendChild(script);",
|
|
800
934
|
" customElements.whenDefined(ELEMENT_NAME).then(function () {",
|
|
@@ -820,8 +954,12 @@ function createSrcdoc(options) {
|
|
|
820
954
|
}
|
|
821
955
|
if (typeof title !== "string") throw new TypeError("[bstage-bridge] title\uC740 \uBB38\uC790\uC5F4\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4");
|
|
822
956
|
const mediaOrigins = normalizeMediaOrigins(options.mediaOrigins);
|
|
957
|
+
const { crossOrigin } = options;
|
|
958
|
+
if (crossOrigin !== void 0 && crossOrigin !== "anonymous") {
|
|
959
|
+
throw new TypeError("[bstage-bridge] crossOrigin\uC740 'anonymous'\uB9CC \uBC1B\uC2B5\uB2C8\uB2E4");
|
|
960
|
+
}
|
|
823
961
|
const csp = buildFrameCsp(nonce, assetOrigin, mediaOrigins);
|
|
824
|
-
const script = bootstrapScript({ session, hostOrigin, entryUrl, elementName });
|
|
962
|
+
const script = bootstrapScript({ session, hostOrigin, entryUrl, elementName, crossOrigin });
|
|
825
963
|
if (/<\/script|<!--/i.test(script)) {
|
|
826
964
|
throw new Error("[bstage-bridge] \uBD80\uD2B8\uC2A4\uD2B8\uB7A9 \uC2A4\uD06C\uB9BD\uD2B8\uC5D0 \uC2A4\uD06C\uB9BD\uD2B8 \uC885\uB8CC \uC2DC\uD000\uC2A4\uAC00 \uC788\uC2B5\uB2C8\uB2E4");
|
|
827
965
|
}
|
|
@@ -860,8 +998,15 @@ ${script}
|
|
|
860
998
|
MAX_ENVELOPE_TYPE_LENGTH,
|
|
861
999
|
MAX_MEDIA_ORIGINS,
|
|
862
1000
|
READY_EVENT,
|
|
1001
|
+
REPORTS_PER_SESSION,
|
|
1002
|
+
REPORT_COUNT_MAX,
|
|
1003
|
+
REPORT_FLUSH_DELAY_MS,
|
|
1004
|
+
REPORT_FRAME_MAX_LENGTH,
|
|
1005
|
+
REPORT_MAX_FRAMES,
|
|
1006
|
+
REPORT_MESSAGE_MAX_LENGTH,
|
|
863
1007
|
REQUEST_TIMEOUT_MS,
|
|
864
1008
|
REQUEST_TYPES,
|
|
1009
|
+
TEMPLATE_REPORT_KINDS,
|
|
865
1010
|
buildFrameCsp,
|
|
866
1011
|
createEnvelope,
|
|
867
1012
|
createNonce,
|