@bstage-sdk/bridge 0.10.0 → 0.12.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/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",
@@ -496,6 +510,232 @@ function createBridgeFetch(client) {
496
510
  return bridgeFetch;
497
511
  }
498
512
 
513
+ // src/frame/directFetch.ts
514
+ function installDirectFetch(g, client, config) {
515
+ const hostOrigin = config.hostOrigin;
516
+ const callFn = Function.prototype.call;
517
+ const NativeURL = g.URL;
518
+ const rawFetch = g.fetch;
519
+ const nativeFetch = typeof rawFetch === "function" ? callFn.bind(rawFetch) : void 0;
520
+ const bridgeFetch = client.fetch;
521
+ const strSlice = callFn.bind(String.prototype.slice);
522
+ const promiseThen = callFn.bind(Promise.prototype.then);
523
+ const promiseReject = Promise.reject.bind(Promise);
524
+ const assign = Object.assign;
525
+ const TypeErr = TypeError;
526
+ function getter(ctor, name) {
527
+ if (typeof ctor !== "function") return void 0;
528
+ let proto = ctor.prototype;
529
+ while (proto) {
530
+ const desc = Object.getOwnPropertyDescriptor(proto, name);
531
+ if (desc && typeof desc.get === "function") {
532
+ return callFn.bind(desc.get);
533
+ }
534
+ proto = Object.getPrototypeOf(proto);
535
+ }
536
+ return void 0;
537
+ }
538
+ const getHref = getter(NativeURL, "href");
539
+ const getOrigin = getter(NativeURL, "origin");
540
+ const getProtocol = getter(NativeURL, "protocol");
541
+ const getPathname = getter(NativeURL, "pathname");
542
+ const getRequestUrl = getter(g.Request, "url");
543
+ let gatewayPrefixes = null;
544
+ let partnerOrigins = [];
545
+ let routesReady = false;
546
+ function copyStrings(value) {
547
+ if (!Array.isArray(value)) return null;
548
+ const out = [];
549
+ for (let i = 0; i < value.length; i += 1) {
550
+ if (typeof value[i] === "string") out[out.length] = value[i];
551
+ }
552
+ return out;
553
+ }
554
+ function takeSnapshot(init) {
555
+ if (routesReady) return;
556
+ const raw = init;
557
+ gatewayPrefixes = copyStrings(raw.gatewayPrefixes);
558
+ partnerOrigins = copyStrings(raw.partnerOrigins) || [];
559
+ routesReady = true;
560
+ }
561
+ promiseThen(client.ready, takeSnapshot);
562
+ function matchesPrefix(pathname, prefix) {
563
+ const bare = strSlice(prefix, prefix.length - 1) === "/" ? strSlice(prefix, 0, -1) : prefix;
564
+ return pathname === bare || strSlice(pathname, 0, bare.length + 1) === bare + "/";
565
+ }
566
+ function routeOf(origin, protocol, pathname) {
567
+ if (origin === hostOrigin) {
568
+ if (gatewayPrefixes === null) return "bridge";
569
+ for (let i = 0; i < gatewayPrefixes.length; i += 1) {
570
+ if (matchesPrefix(pathname, gatewayPrefixes[i])) return "bridge";
571
+ }
572
+ return "reject";
573
+ }
574
+ for (let i = 0; i < partnerOrigins.length; i += 1) {
575
+ if (origin === partnerOrigins[i]) return "bridge";
576
+ }
577
+ return protocol === "https:" ? "direct" : "reject";
578
+ }
579
+ function requestUrlOf(input) {
580
+ if (!getRequestUrl || typeof input !== "object" || input === null) return void 0;
581
+ try {
582
+ return getRequestUrl(input);
583
+ } catch {
584
+ return void 0;
585
+ }
586
+ }
587
+ function dispatch(input, init) {
588
+ if (typeof NativeURL !== "function" || !getHref || !getOrigin || !getProtocol || !getPathname) {
589
+ return promiseReject(new TypeErr("bstage fetch: URL is unavailable"));
590
+ }
591
+ const requestUrl = requestUrlOf(input);
592
+ const request = requestUrl !== void 0;
593
+ let parsed;
594
+ try {
595
+ parsed = new NativeURL(request ? requestUrl : String(input), hostOrigin);
596
+ } catch {
597
+ return promiseReject(new TypeErr("bstage fetch: invalid url"));
598
+ }
599
+ const href = getHref(parsed);
600
+ const route = routeOf(getOrigin(parsed), getProtocol(parsed), getPathname(parsed));
601
+ const target = request ? input : href;
602
+ if (route === "bridge") return bridgeFetch(target, init);
603
+ if (route === "reject") {
604
+ return promiseReject(new TypeErr("bstage fetch: destination not allowed"));
605
+ }
606
+ if (!nativeFetch) return promiseReject(new TypeErr("bstage fetch: fetch is unavailable"));
607
+ return nativeFetch(
608
+ g,
609
+ target,
610
+ assign({}, init, { credentials: "omit", referrerPolicy: "no-referrer" })
611
+ );
612
+ }
613
+ function bstageFetch(input, init) {
614
+ try {
615
+ if (routesReady) return dispatch(input, init);
616
+ return promiseThen(client.ready, function(value) {
617
+ takeSnapshot(value);
618
+ return dispatch(input, init);
619
+ });
620
+ } catch {
621
+ return promiseReject(new TypeErr("bstage fetch: request failed"));
622
+ }
623
+ }
624
+ Object.defineProperty(g, "__bstage_fetch__", {
625
+ value: bstageFetch,
626
+ writable: false,
627
+ configurable: false,
628
+ enumerable: false
629
+ });
630
+ return bstageFetch;
631
+ }
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
+
499
739
  // src/srcdoc/escape.ts
500
740
  var HTML_ESCAPES = {
501
741
  "&": "&amp;",
@@ -588,6 +828,7 @@ var LOCAL_HOST_NAMES = Object.freeze([
588
828
 
589
829
  // src/srcdoc/createSrcdoc.ts
590
830
  var MAX_MEDIA_ORIGINS = 20;
831
+ var REPORT_FLUSH_DELAY_MS = 1e3;
591
832
  var MAX_ELEMENT_NAME_LENGTH = 100;
592
833
  var ELEMENT_NAME_RE = /^[a-z][a-z0-9._]*-[a-z0-9._-]*$/;
593
834
  function isValidElementName(value) {
@@ -636,7 +877,7 @@ function buildFrameCsp(nonce, assetOrigin, mediaOrigins = []) {
636
877
  `img-src ${media} data: blob:`,
637
878
  `font-src ${assetOrigin} data:`,
638
879
  `media-src ${media} blob:`,
639
- "connect-src 'none'",
880
+ "connect-src https: wss:",
640
881
  "frame-src 'none'",
641
882
  "form-action 'none'",
642
883
  "base-uri 'none'"
@@ -653,6 +894,19 @@ function bootstrapScript(o) {
653
894
  `var bridgeRuntime = ${bridgeRuntime.toString()};`,
654
895
  `var createBridgeFetch = ${createBridgeFetch.toString()};`,
655
896
  "var client = bridgeRuntime(globalThis, createBridgeFetch, { session: SESSION, hostOrigin: HOST_ORIGIN });",
897
+ // 라우터는 항상 심는다. ready.then이 엔트리 로드(아래 client.ready.then)보다 먼저 등록돼야 템플릿보다 먼저 스냅샷을 잡는다.
898
+ `var installDirectFetch = ${installDirectFetch.toString()};`,
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
+ "});",
656
910
  "var lastHeight = -1;",
657
911
  "var scheduled = false;",
658
912
  "function reportHeight() {",
@@ -669,9 +923,12 @@ function bootstrapScript(o) {
669
923
  "}",
670
924
  "client.ready.then(function () {",
671
925
  " var script = document.createElement('script');",
926
+ // crossOrigin은 src보다 먼저 둔다(요청 모드가 src 대입 시점에 정해지는 브라우저가 있다). 주지 않으면 줄 자체가 없다.
927
+ ...o.crossOrigin === "anonymous" ? [" script.crossOrigin = 'anonymous';"] : [],
672
928
  " script.src = ENTRY_URL;",
673
929
  " script.onerror = function () {",
674
930
  " client.request('log', { level: 'error', message: 'template entry failed to load' }).catch(function () {});",
931
+ " reportError('load', 'template entry failed to load', '');",
675
932
  " };",
676
933
  " document.head.appendChild(script);",
677
934
  " customElements.whenDefined(ELEMENT_NAME).then(function () {",
@@ -697,8 +954,12 @@ function createSrcdoc(options) {
697
954
  }
698
955
  if (typeof title !== "string") throw new TypeError("[bstage-bridge] title\uC740 \uBB38\uC790\uC5F4\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4");
699
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
+ }
700
961
  const csp = buildFrameCsp(nonce, assetOrigin, mediaOrigins);
701
- const script = bootstrapScript({ session, hostOrigin, entryUrl, elementName });
962
+ const script = bootstrapScript({ session, hostOrigin, entryUrl, elementName, crossOrigin });
702
963
  if (/<\/script|<!--/i.test(script)) {
703
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");
704
965
  }
@@ -737,8 +998,15 @@ ${script}
737
998
  MAX_ENVELOPE_TYPE_LENGTH,
738
999
  MAX_MEDIA_ORIGINS,
739
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,
740
1007
  REQUEST_TIMEOUT_MS,
741
1008
  REQUEST_TYPES,
1009
+ TEMPLATE_REPORT_KINDS,
742
1010
  buildFrameCsp,
743
1011
  createEnvelope,
744
1012
  createNonce,
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
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
- 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';
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 REPORTS_PER_SESSION, r as REPORT_COUNT_MAX, s as REPORT_FRAME_MAX_LENGTH, t as REPORT_MAX_FRAMES, u as REPORT_MESSAGE_MAX_LENGTH, v as REQUEST_TIMEOUT_MS, w as REQUEST_TYPES, S as SlotState, x as StorageOp, T as TEMPLATE_REPORT_KINDS, y as TemplateReport, z as TemplateReportKind, A as ThemeState, C as ToastKind, W as WithSession, D as isBridgeErrorCode, E as isBridgeErrorLike, G as isBridgeSurface, J as isHostEventType, K as isRequestType } from './messages-Cdjpvu2S.cjs';
2
+ export { C as CreateSrcdocOptions, M as MAX_ELEMENT_NAME_LENGTH, a as MAX_MEDIA_ORIGINS, R as REPORT_FLUSH_DELAY_MS, b as buildFrameCsp, c as createNonce, d as createSession, e as createSrcdoc, i as isValidNonce, f as isValidSession } from './createSrcdoc-DYZk6dDJ.cjs';
3
3
 
4
4
  /** 봉투 버전. 형식이 하위 호환되지 않게 바뀔 때만 올린다. */
5
5
  declare const BRIDGE_VERSION = 1;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
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
- 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';
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 REPORTS_PER_SESSION, r as REPORT_COUNT_MAX, s as REPORT_FRAME_MAX_LENGTH, t as REPORT_MAX_FRAMES, u as REPORT_MESSAGE_MAX_LENGTH, v as REQUEST_TIMEOUT_MS, w as REQUEST_TYPES, S as SlotState, x as StorageOp, T as TEMPLATE_REPORT_KINDS, y as TemplateReport, z as TemplateReportKind, A as ThemeState, C as ToastKind, W as WithSession, D as isBridgeErrorCode, E as isBridgeErrorLike, G as isBridgeSurface, J as isHostEventType, K as isRequestType } from './messages-Cdjpvu2S.js';
2
+ export { C as CreateSrcdocOptions, M as MAX_ELEMENT_NAME_LENGTH, a as MAX_MEDIA_ORIGINS, R as REPORT_FLUSH_DELAY_MS, b as buildFrameCsp, c as createNonce, d as createSession, e as createSrcdoc, i as isValidNonce, f as isValidSession } from './createSrcdoc-DYZk6dDJ.js';
3
3
 
4
4
  /** 봉투 버전. 형식이 하위 호환되지 않게 바뀔 때만 올린다. */
5
5
  declare const BRIDGE_VERSION = 1;
package/dist/index.js CHANGED
@@ -13,8 +13,15 @@ import {
13
13
  MAX_ENVELOPE_TYPE_LENGTH,
14
14
  MAX_MEDIA_ORIGINS,
15
15
  READY_EVENT,
16
+ REPORTS_PER_SESSION,
17
+ REPORT_COUNT_MAX,
18
+ REPORT_FLUSH_DELAY_MS,
19
+ REPORT_FRAME_MAX_LENGTH,
20
+ REPORT_MAX_FRAMES,
21
+ REPORT_MESSAGE_MAX_LENGTH,
16
22
  REQUEST_TIMEOUT_MS,
17
23
  REQUEST_TYPES,
24
+ TEMPLATE_REPORT_KINDS,
18
25
  buildFrameCsp,
19
26
  createEnvelope,
20
27
  createNonce,
@@ -29,8 +36,8 @@ import {
29
36
  isRequestType,
30
37
  isValidNonce,
31
38
  isValidSession
32
- } from "./chunk-JH3A3Z2A.js";
33
- import "./chunk-N3JID7KQ.js";
39
+ } from "./chunk-NXB47KPE.js";
40
+ import "./chunk-TPKNGW34.js";
34
41
  export {
35
42
  BRIDGE_ERROR_CODES,
36
43
  BRIDGE_NAMESPACE,
@@ -46,8 +53,15 @@ export {
46
53
  MAX_ENVELOPE_TYPE_LENGTH,
47
54
  MAX_MEDIA_ORIGINS,
48
55
  READY_EVENT,
56
+ REPORTS_PER_SESSION,
57
+ REPORT_COUNT_MAX,
58
+ REPORT_FLUSH_DELAY_MS,
59
+ REPORT_FRAME_MAX_LENGTH,
60
+ REPORT_MAX_FRAMES,
61
+ REPORT_MESSAGE_MAX_LENGTH,
49
62
  REQUEST_TIMEOUT_MS,
50
63
  REQUEST_TYPES,
64
+ TEMPLATE_REPORT_KINDS,
51
65
  buildFrameCsp,
52
66
  createEnvelope,
53
67
  createNonce,
@@ -19,7 +19,7 @@ declare function isBridgeErrorLike(value: unknown): value is BridgeErrorLike;
19
19
  /** 프레임 요청 타임아웃. */
20
20
  declare const REQUEST_TIMEOUT_MS = 15000;
21
21
  /** 프레임 → 호스트 요청 종류(브리지 프로토콜 표). */
22
- declare const REQUEST_TYPES: readonly ["fetch", "navigate", "back", "openExternal", "toast", "resize", "track", "storage", "log"];
22
+ declare const REQUEST_TYPES: readonly ["fetch", "navigate", "back", "openExternal", "toast", "resize", "track", "storage", "log", "report"];
23
23
  type BridgeRequestType = (typeof REQUEST_TYPES)[number];
24
24
  /** 호스트 → 프레임 이벤트 종류. */
25
25
  declare const HOST_EVENT_TYPES: readonly ["slot.context", "locale", "member", "theme", "visibility"];
@@ -70,6 +70,12 @@ interface BridgeInit {
70
70
  readonly spaceId: string;
71
71
  readonly hostOrigin: string;
72
72
  readonly gatewayBase: string;
73
+ /**
74
+ * 호스트 정책의 같은 origin 게이트웨이 경로 접두사(`policy.gatewayPrefixes`). 프레임
75
+ * 라우터(`__bstage_fetch__`)가 "브리지로 보낼 같은 origin 경로"를 가르는 데 쓴다. 판정의 정본은 여전히 호스트다.
76
+ * 이 필드가 없는 옛 호스트면 라우터는 hostOrigin 요청을 모두 브리지로 보낸다.
77
+ */
78
+ readonly gatewayPrefixes?: readonly string[];
73
79
  readonly locale: string;
74
80
  readonly theme: ThemeState;
75
81
  readonly slot?: SlotState;
@@ -95,6 +101,29 @@ interface FetchResult {
95
101
  type ToastKind = 'info' | 'success' | 'warning' | 'error';
96
102
  type LogLevel = 'debug' | 'info' | 'warn' | 'error';
97
103
  type StorageOp = 'get' | 'set' | 'remove';
104
+ /** 템플릿 에러 보고 종류 — 전역 에러, 처리 안 된 Promise 거절, 엔트리 스크립트 로드 실패. */
105
+ declare const TEMPLATE_REPORT_KINDS: readonly ["error", "rejection", "load"];
106
+ type TemplateReportKind = (typeof TEMPLATE_REPORT_KINDS)[number];
107
+ /** report message 최대 길이(자). 프레임 수집기는 이 길이로 잘라 보내고, 호스트는 넘으면 거절한다. */
108
+ declare const REPORT_MESSAGE_MAX_LENGTH = 300;
109
+ /** report frames 최대 개수. */
110
+ declare const REPORT_MAX_FRAMES = 10;
111
+ /** report frame 한 줄 최대 길이(자). */
112
+ declare const REPORT_FRAME_MAX_LENGTH = 200;
113
+ /** report count 상한(같은 fingerprint 누적 횟수). 하한은 1. */
114
+ declare const REPORT_COUNT_MAX = 100;
115
+ /** 프레임 세션당 report 상한. 프레임 수집기와 호스트가 각자 지킨다. */
116
+ declare const REPORTS_PER_SESSION = 5;
117
+ /**
118
+ * `report` payload — 격리 프레임이 잡은 템플릿 에러 하나(같은 fingerprint는 count로 묶는다).
119
+ * URL·placement 같은 귀속 정보는 싣지 않는다 — 호스트가 자기가 띄운 템플릿으로 귀속한다.
120
+ */
121
+ interface TemplateReport {
122
+ readonly kind: TemplateReportKind;
123
+ readonly message: string;
124
+ readonly frames: readonly string[];
125
+ readonly count: number;
126
+ }
98
127
  /** 요청 type별 payload·결과 타입. */
99
128
  interface BridgeRequestMap {
100
129
  fetch: {
@@ -154,6 +183,10 @@ interface BridgeRequestMap {
154
183
  };
155
184
  res: Record<string, never>;
156
185
  };
186
+ report: {
187
+ req: TemplateReport;
188
+ res: Record<string, never>;
189
+ };
157
190
  }
158
191
  /** 이벤트 type별 payload. */
159
192
  interface BridgeEventMap {
@@ -188,4 +221,4 @@ declare const IFRAME_ATTRIBUTES: {
188
221
  declare function isRequestType(value: unknown): value is BridgeRequestType;
189
222
  declare function isHostEventType(value: unknown): value is BridgeHostEventType;
190
223
 
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 };
224
+ export { type ThemeState as A, BRIDGE_ERROR_CODES as B, type ToastKind as C, isBridgeErrorCode as D, isBridgeErrorLike as E, FRAME_UNLOAD_EVENT as F, isBridgeSurface as G, HOST_EVENT_TYPES as H, IFRAME_ATTRIBUTES as I, isHostEventType as J, isRequestType as K, type LogLevel as L, type MemberState as M, READY_EVENT as R, type SlotState as S, TEMPLATE_REPORT_KINDS 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, REPORTS_PER_SESSION as q, REPORT_COUNT_MAX as r, REPORT_FRAME_MAX_LENGTH as s, REPORT_MAX_FRAMES as t, REPORT_MESSAGE_MAX_LENGTH as u, REQUEST_TIMEOUT_MS as v, REQUEST_TYPES as w, type StorageOp as x, type TemplateReport as y, type TemplateReportKind as z };
@@ -19,7 +19,7 @@ declare function isBridgeErrorLike(value: unknown): value is BridgeErrorLike;
19
19
  /** 프레임 요청 타임아웃. */
20
20
  declare const REQUEST_TIMEOUT_MS = 15000;
21
21
  /** 프레임 → 호스트 요청 종류(브리지 프로토콜 표). */
22
- declare const REQUEST_TYPES: readonly ["fetch", "navigate", "back", "openExternal", "toast", "resize", "track", "storage", "log"];
22
+ declare const REQUEST_TYPES: readonly ["fetch", "navigate", "back", "openExternal", "toast", "resize", "track", "storage", "log", "report"];
23
23
  type BridgeRequestType = (typeof REQUEST_TYPES)[number];
24
24
  /** 호스트 → 프레임 이벤트 종류. */
25
25
  declare const HOST_EVENT_TYPES: readonly ["slot.context", "locale", "member", "theme", "visibility"];
@@ -70,6 +70,12 @@ interface BridgeInit {
70
70
  readonly spaceId: string;
71
71
  readonly hostOrigin: string;
72
72
  readonly gatewayBase: string;
73
+ /**
74
+ * 호스트 정책의 같은 origin 게이트웨이 경로 접두사(`policy.gatewayPrefixes`). 프레임
75
+ * 라우터(`__bstage_fetch__`)가 "브리지로 보낼 같은 origin 경로"를 가르는 데 쓴다. 판정의 정본은 여전히 호스트다.
76
+ * 이 필드가 없는 옛 호스트면 라우터는 hostOrigin 요청을 모두 브리지로 보낸다.
77
+ */
78
+ readonly gatewayPrefixes?: readonly string[];
73
79
  readonly locale: string;
74
80
  readonly theme: ThemeState;
75
81
  readonly slot?: SlotState;
@@ -95,6 +101,29 @@ interface FetchResult {
95
101
  type ToastKind = 'info' | 'success' | 'warning' | 'error';
96
102
  type LogLevel = 'debug' | 'info' | 'warn' | 'error';
97
103
  type StorageOp = 'get' | 'set' | 'remove';
104
+ /** 템플릿 에러 보고 종류 — 전역 에러, 처리 안 된 Promise 거절, 엔트리 스크립트 로드 실패. */
105
+ declare const TEMPLATE_REPORT_KINDS: readonly ["error", "rejection", "load"];
106
+ type TemplateReportKind = (typeof TEMPLATE_REPORT_KINDS)[number];
107
+ /** report message 최대 길이(자). 프레임 수집기는 이 길이로 잘라 보내고, 호스트는 넘으면 거절한다. */
108
+ declare const REPORT_MESSAGE_MAX_LENGTH = 300;
109
+ /** report frames 최대 개수. */
110
+ declare const REPORT_MAX_FRAMES = 10;
111
+ /** report frame 한 줄 최대 길이(자). */
112
+ declare const REPORT_FRAME_MAX_LENGTH = 200;
113
+ /** report count 상한(같은 fingerprint 누적 횟수). 하한은 1. */
114
+ declare const REPORT_COUNT_MAX = 100;
115
+ /** 프레임 세션당 report 상한. 프레임 수집기와 호스트가 각자 지킨다. */
116
+ declare const REPORTS_PER_SESSION = 5;
117
+ /**
118
+ * `report` payload — 격리 프레임이 잡은 템플릿 에러 하나(같은 fingerprint는 count로 묶는다).
119
+ * URL·placement 같은 귀속 정보는 싣지 않는다 — 호스트가 자기가 띄운 템플릿으로 귀속한다.
120
+ */
121
+ interface TemplateReport {
122
+ readonly kind: TemplateReportKind;
123
+ readonly message: string;
124
+ readonly frames: readonly string[];
125
+ readonly count: number;
126
+ }
98
127
  /** 요청 type별 payload·결과 타입. */
99
128
  interface BridgeRequestMap {
100
129
  fetch: {
@@ -154,6 +183,10 @@ interface BridgeRequestMap {
154
183
  };
155
184
  res: Record<string, never>;
156
185
  };
186
+ report: {
187
+ req: TemplateReport;
188
+ res: Record<string, never>;
189
+ };
157
190
  }
158
191
  /** 이벤트 type별 payload. */
159
192
  interface BridgeEventMap {
@@ -188,4 +221,4 @@ declare const IFRAME_ATTRIBUTES: {
188
221
  declare function isRequestType(value: unknown): value is BridgeRequestType;
189
222
  declare function isHostEventType(value: unknown): value is BridgeHostEventType;
190
223
 
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 };
224
+ export { type ThemeState as A, BRIDGE_ERROR_CODES as B, type ToastKind as C, isBridgeErrorCode as D, isBridgeErrorLike as E, FRAME_UNLOAD_EVENT as F, isBridgeSurface as G, HOST_EVENT_TYPES as H, IFRAME_ATTRIBUTES as I, isHostEventType as J, isRequestType as K, type LogLevel as L, type MemberState as M, READY_EVENT as R, type SlotState as S, TEMPLATE_REPORT_KINDS 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, REPORTS_PER_SESSION as q, REPORT_COUNT_MAX as r, REPORT_FRAME_MAX_LENGTH as s, REPORT_MAX_FRAMES as t, REPORT_MESSAGE_MAX_LENGTH as u, REQUEST_TIMEOUT_MS as v, REQUEST_TYPES as w, type StorageOp as x, type TemplateReport as y, type TemplateReportKind as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bstage-sdk/bridge",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "bstage SDK bridge — isolated template iframe protocol, host policy and frame client",
5
5
  "type": "module",
6
6
  "publishConfig": {