@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/frame.cjs CHANGED
@@ -21,7 +21,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var frame_exports = {};
22
22
  __export(frame_exports, {
23
23
  createBridgeClient: () => createBridgeClient,
24
- createBridgeFetch: () => createBridgeFetch
24
+ createBridgeFetch: () => createBridgeFetch,
25
+ installDirectFetch: () => installDirectFetch,
26
+ installErrorReport: () => installErrorReport
25
27
  });
26
28
  module.exports = __toCommonJS(frame_exports);
27
29
 
@@ -379,8 +381,236 @@ function createBridgeClient(options = {}) {
379
381
  }
380
382
  return bridgeRuntime(g, createBridgeFetch, { session, hostOrigin });
381
383
  }
384
+
385
+ // src/frame/directFetch.ts
386
+ function installDirectFetch(g, client, config) {
387
+ const hostOrigin = config.hostOrigin;
388
+ const callFn = Function.prototype.call;
389
+ const NativeURL = g.URL;
390
+ const rawFetch = g.fetch;
391
+ const nativeFetch = typeof rawFetch === "function" ? callFn.bind(rawFetch) : void 0;
392
+ const bridgeFetch = client.fetch;
393
+ const strSlice = callFn.bind(String.prototype.slice);
394
+ const promiseThen = callFn.bind(Promise.prototype.then);
395
+ const promiseReject = Promise.reject.bind(Promise);
396
+ const assign = Object.assign;
397
+ const TypeErr = TypeError;
398
+ function getter(ctor, name) {
399
+ if (typeof ctor !== "function") return void 0;
400
+ let proto = ctor.prototype;
401
+ while (proto) {
402
+ const desc = Object.getOwnPropertyDescriptor(proto, name);
403
+ if (desc && typeof desc.get === "function") {
404
+ return callFn.bind(desc.get);
405
+ }
406
+ proto = Object.getPrototypeOf(proto);
407
+ }
408
+ return void 0;
409
+ }
410
+ const getHref = getter(NativeURL, "href");
411
+ const getOrigin = getter(NativeURL, "origin");
412
+ const getProtocol = getter(NativeURL, "protocol");
413
+ const getPathname = getter(NativeURL, "pathname");
414
+ const getRequestUrl = getter(g.Request, "url");
415
+ let gatewayPrefixes = null;
416
+ let partnerOrigins = [];
417
+ let routesReady = false;
418
+ function copyStrings(value) {
419
+ if (!Array.isArray(value)) return null;
420
+ const out = [];
421
+ for (let i = 0; i < value.length; i += 1) {
422
+ if (typeof value[i] === "string") out[out.length] = value[i];
423
+ }
424
+ return out;
425
+ }
426
+ function takeSnapshot(init) {
427
+ if (routesReady) return;
428
+ const raw = init;
429
+ gatewayPrefixes = copyStrings(raw.gatewayPrefixes);
430
+ partnerOrigins = copyStrings(raw.partnerOrigins) || [];
431
+ routesReady = true;
432
+ }
433
+ promiseThen(client.ready, takeSnapshot);
434
+ function matchesPrefix(pathname, prefix) {
435
+ const bare = strSlice(prefix, prefix.length - 1) === "/" ? strSlice(prefix, 0, -1) : prefix;
436
+ return pathname === bare || strSlice(pathname, 0, bare.length + 1) === bare + "/";
437
+ }
438
+ function routeOf(origin, protocol, pathname) {
439
+ if (origin === hostOrigin) {
440
+ if (gatewayPrefixes === null) return "bridge";
441
+ for (let i = 0; i < gatewayPrefixes.length; i += 1) {
442
+ if (matchesPrefix(pathname, gatewayPrefixes[i])) return "bridge";
443
+ }
444
+ return "reject";
445
+ }
446
+ for (let i = 0; i < partnerOrigins.length; i += 1) {
447
+ if (origin === partnerOrigins[i]) return "bridge";
448
+ }
449
+ return protocol === "https:" ? "direct" : "reject";
450
+ }
451
+ function requestUrlOf(input) {
452
+ if (!getRequestUrl || typeof input !== "object" || input === null) return void 0;
453
+ try {
454
+ return getRequestUrl(input);
455
+ } catch {
456
+ return void 0;
457
+ }
458
+ }
459
+ function dispatch(input, init) {
460
+ if (typeof NativeURL !== "function" || !getHref || !getOrigin || !getProtocol || !getPathname) {
461
+ return promiseReject(new TypeErr("bstage fetch: URL is unavailable"));
462
+ }
463
+ const requestUrl = requestUrlOf(input);
464
+ const request = requestUrl !== void 0;
465
+ let parsed;
466
+ try {
467
+ parsed = new NativeURL(request ? requestUrl : String(input), hostOrigin);
468
+ } catch {
469
+ return promiseReject(new TypeErr("bstage fetch: invalid url"));
470
+ }
471
+ const href = getHref(parsed);
472
+ const route = routeOf(getOrigin(parsed), getProtocol(parsed), getPathname(parsed));
473
+ const target = request ? input : href;
474
+ if (route === "bridge") return bridgeFetch(target, init);
475
+ if (route === "reject") {
476
+ return promiseReject(new TypeErr("bstage fetch: destination not allowed"));
477
+ }
478
+ if (!nativeFetch) return promiseReject(new TypeErr("bstage fetch: fetch is unavailable"));
479
+ return nativeFetch(
480
+ g,
481
+ target,
482
+ assign({}, init, { credentials: "omit", referrerPolicy: "no-referrer" })
483
+ );
484
+ }
485
+ function bstageFetch(input, init) {
486
+ try {
487
+ if (routesReady) return dispatch(input, init);
488
+ return promiseThen(client.ready, function(value) {
489
+ takeSnapshot(value);
490
+ return dispatch(input, init);
491
+ });
492
+ } catch {
493
+ return promiseReject(new TypeErr("bstage fetch: request failed"));
494
+ }
495
+ }
496
+ Object.defineProperty(g, "__bstage_fetch__", {
497
+ value: bstageFetch,
498
+ writable: false,
499
+ configurable: false,
500
+ enumerable: false
501
+ });
502
+ return bstageFetch;
503
+ }
504
+
505
+ // src/frame/errorReport.ts
506
+ function installErrorReport(g, client, config) {
507
+ const schedule = typeof g.setTimeout === "function" ? g.setTimeout : void 0;
508
+ const entries = /* @__PURE__ */ new Map();
509
+ let admitted = 0;
510
+ function toText(value) {
511
+ if (typeof value === "string") return value;
512
+ try {
513
+ return String(value);
514
+ } catch {
515
+ return "";
516
+ }
517
+ }
518
+ function framesOf(stack) {
519
+ const out = [];
520
+ if (typeof stack !== "string") return out;
521
+ const lines = stack.split("\n");
522
+ for (let i = 0; i < lines.length && out.length < config.maxFrames; i += 1) {
523
+ const line = lines[i].trim();
524
+ if (/^at\s/.test(line) || /^[^@]*@(?:[a-z][\w+.-]*:\/\/|\/)\S*:\d+:\d+$/i.test(line) || /^(?:[a-z][\w+.-]*:\/\/|\/)\S*:\d+:\d+$/i.test(line))
525
+ out.push(line.slice(0, config.maxFrameLength));
526
+ }
527
+ return out;
528
+ }
529
+ function send(key) {
530
+ const entry = entries.get(key);
531
+ if (!entry || entry.sent) return;
532
+ entries.set(key, {
533
+ kind: entry.kind,
534
+ message: entry.message,
535
+ frames: entry.frames,
536
+ count: entry.count,
537
+ sent: true
538
+ });
539
+ try {
540
+ client.request("report", {
541
+ kind: entry.kind,
542
+ message: entry.message,
543
+ frames: entry.frames,
544
+ count: entry.count
545
+ }).catch(function() {
546
+ });
547
+ } catch {
548
+ }
549
+ }
550
+ function record(kind, message, stack) {
551
+ try {
552
+ const text = toText(message).slice(0, config.maxMessageLength);
553
+ const frames = framesOf(stack);
554
+ const key = kind + "|" + text.replace(/\d+/g, "") + "|" + (frames[0] || "");
555
+ const entry = entries.get(key);
556
+ if (entry) {
557
+ if (!entry.sent && entry.count < config.maxCount) {
558
+ entries.set(key, {
559
+ kind: entry.kind,
560
+ message: entry.message,
561
+ frames: entry.frames,
562
+ count: entry.count + 1,
563
+ sent: false
564
+ });
565
+ }
566
+ return;
567
+ }
568
+ if (admitted >= config.maxReports) return;
569
+ admitted += 1;
570
+ entries.set(key, { kind, message: text, frames, count: 1, sent: false });
571
+ if (admitted >= config.maxReports) stop();
572
+ if (schedule) {
573
+ schedule(function() {
574
+ send(key);
575
+ }, config.flushDelayMs);
576
+ } else {
577
+ send(key);
578
+ }
579
+ } catch {
580
+ }
581
+ }
582
+ function onError(ev) {
583
+ const err = ev && ev.error;
584
+ if (err && typeof err === "object") {
585
+ record("error", typeof err.message === "string" ? err.message : ev.message, err.stack);
586
+ return;
587
+ }
588
+ const where = ev && typeof ev.filename === "string" && ev.filename !== "" ? "at " + ev.filename + ":" + toText(ev.lineno) + ":" + toText(ev.colno) : "";
589
+ record("error", ev ? ev.message : "", where);
590
+ }
591
+ function onRejection(ev) {
592
+ const reason = ev ? ev.reason : void 0;
593
+ if (reason && typeof reason === "object" && typeof reason.message === "string") {
594
+ record("rejection", reason.message, reason.stack);
595
+ return;
596
+ }
597
+ record("rejection", reason, "");
598
+ }
599
+ function stop() {
600
+ if (typeof g.removeEventListener !== "function") return;
601
+ g.removeEventListener("error", onError);
602
+ g.removeEventListener("unhandledrejection", onRejection);
603
+ }
604
+ if (typeof g.addEventListener === "function") {
605
+ g.addEventListener("error", onError);
606
+ g.addEventListener("unhandledrejection", onRejection);
607
+ }
608
+ return record;
609
+ }
382
610
  // Annotate the CommonJS export names for ESM import in node:
383
611
  0 && (module.exports = {
384
612
  createBridgeClient,
385
- createBridgeFetch
613
+ createBridgeFetch,
614
+ installDirectFetch,
615
+ installErrorReport
386
616
  });
package/dist/frame.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { g as BridgeInit, j as BridgeRequestType, i as BridgeRequestMap, e as BridgeEventMap } from './messages-CRGnyf-6.cjs';
1
+ import { g as BridgeInit, j as BridgeRequestType, i as BridgeRequestMap, e as BridgeEventMap, z as TemplateReportKind } from './messages-Cdjpvu2S.cjs';
2
2
 
3
3
  /** 프레임 런타임 설정 — srcdoc 부트스트랩이 상수로 넘긴다. */
4
4
  interface BridgeClientConfig {
@@ -73,4 +73,89 @@ declare function createBridgeClient(options?: CreateBridgeClientOptions): Bridge
73
73
  */
74
74
  declare function createBridgeFetch(client: BridgeRequester): typeof globalThis.fetch;
75
75
 
76
- export { type BridgeClient, type BridgeClientConfig, type BridgeRequester, type CreateBridgeClientOptions, type Unsubscribe, createBridgeClient, createBridgeFetch };
76
+ /** `installDirectFetch` 설정 — srcdoc 부트스트랩이 상수로 넘긴다. */
77
+ interface DirectFetchConfig {
78
+ /** 호스트 페이지 origin(srcdoc에 심긴 값). 상대 URL의 기준이자 "같은 origin" 판정 기준. */
79
+ readonly hostOrigin: string;
80
+ }
81
+ /** 프레임 fetch 라우터가 쓰는 전역의 최소 모양 — 테스트에서 가짜 창을 넘길 수 있게 한다. */
82
+ interface DirectFetchGlobal {
83
+ fetch?: typeof globalThis.fetch;
84
+ URL?: typeof URL;
85
+ Request?: typeof Request;
86
+ __bstage_fetch__?: typeof globalThis.fetch;
87
+ }
88
+ /** 갈래 판정 결과. */
89
+ type DirectRoute = 'bridge' | 'direct' | 'reject';
90
+ /**
91
+ * 격리 프레임의 fetch 라우터를(`createSrcdoc` 부트스트랩이 항상 설치한다, README "프레임 네트워크")
92
+ * `globalThis.__bstage_fetch__`에 설치한다. inline 모드용으로 쓰인 템플릿이 부르는 그 이름이다.
93
+ *
94
+ * 갈래(`new URL(input, hostOrigin)`으로 정규화한 뒤):
95
+ * - **브리지**(토큰이 필요한 목적지): hostOrigin과 같은 origin이고 경로가 init `gatewayPrefixes` 안, 또는
96
+ * origin이 init `partnerOrigins`와 정확히 일치. 기존 브리지 fetch(`client.fetch`)로 보내고 호스트가 다시 판정한다.
97
+ * - **직접**: 그 밖의 https. 부트스트랩 때 잡아 둔 프레임의 원래 `fetch`로 `credentials: 'omit'`·
98
+ * `referrerPolicy: 'no-referrer'`를 덮어 부른다. 인증 헤더는 붙이지 않는다(토큰은 프레임에 없다).
99
+ * - **거절**(`TypeError`): hostOrigin의 게이트웨이 밖 경로(혼동 방지 — 직접 보내지 않는다), https가 아닌 스킴.
100
+ *
101
+ * **자기완결 함수다** — `bridgeRuntime`과 함께 srcdoc에 `toString()`으로 심긴다(runtime.ts 주석 참고).
102
+ * 템플릿 코드보다 먼저 돌고, 판정·전송에 쓰는 전역(`fetch`·`URL`과 그 접근자·`String.prototype.slice`·
103
+ * `Promise.prototype.then`·`Object.assign`·`TypeError`)과 init의 라우팅 목록을 이때 잡아 둔다. 그래서 템플릿이
104
+ * 나중에 전역·프로토타입·init 객체를 바꿔도 갈래가 바뀌지 않는다. 갈래가 바뀌어도 토큰은 새지 않는다 —
105
+ * 브리지로 간 요청은 호스트가 따로 판정하고, 직접 간 요청에는 토큰이 없다. 이 고정은 혼동 방지다.
106
+ *
107
+ * init이 `gatewayPrefixes`를 싣지 않으면(옛 호스트) hostOrigin 요청은 경로와 무관하게 브리지로 보낸다
108
+ * (호스트가 판정하므로 안전한 쪽).
109
+ *
110
+ * @param g 프레임 전역(`globalThis`)
111
+ * @param client 프레임 브리지 클라이언트(`bridgeRuntime` 결과, frozen)
112
+ * @param config srcdoc에 심긴 hostOrigin
113
+ * @returns 설치한 fetch 라우터
114
+ */
115
+ declare function installDirectFetch(g: DirectFetchGlobal, client: Pick<BridgeClient, 'fetch' | 'ready'>, config: DirectFetchConfig): typeof globalThis.fetch;
116
+
117
+ /** `installErrorReport` 설정 — srcdoc 부트스트랩이 프로토콜 상한을 상수로 넘긴다. */
118
+ interface ErrorReportConfig {
119
+ /** 세션당 서로 다른 fingerprint 상한. 닿으면 리스너를 해제한다. */
120
+ readonly maxReports: number;
121
+ readonly maxMessageLength: number;
122
+ readonly maxFrames: number;
123
+ readonly maxFrameLength: number;
124
+ /** 같은 fingerprint 누적 횟수 상한. */
125
+ readonly maxCount: number;
126
+ /** 첫 발생 뒤 보내기까지 기다리는 시간(ms). 그 사이 같은 fingerprint는 count로 묶인다. */
127
+ readonly flushDelayMs: number;
128
+ }
129
+ /** 수집기가 쓰는 전역의 최소 모양 — 테스트에서 가짜 창을 넘길 수 있게 한다. */
130
+ interface ErrorReportGlobal {
131
+ addEventListener?(type: 'error' | 'unhandledrejection', fn: (ev: never) => void): void;
132
+ removeEventListener?(type: 'error' | 'unhandledrejection', fn: (ev: never) => void): void;
133
+ setTimeout?: (fn: () => void, ms: number) => unknown;
134
+ }
135
+ /** 수집기에 직접 넣는 함수 — 엔트리 스크립트 load 실패처럼 이벤트가 아닌 것을 보고할 때 쓴다. */
136
+ type ErrorReportRecord = (kind: TemplateReportKind, message: unknown, stack: unknown) => void;
137
+ /**
138
+ * 격리 프레임의 템플릿 에러 수집기를 설치한다(`createSrcdoc` 부트스트랩이 항상 설치한다).
139
+ *
140
+ * - 전역 `error`·`unhandledrejection`을 듣고, 반환 함수로 엔트리 load 실패(kind `load`)도 받는다.
141
+ * - 정규화: message는 문자열로 바꿔 `maxMessageLength`에서 자르고, 스택은 줄로 나눠 프레임 꼴(`at …`·`f@url`·
142
+ * `…:줄:열`)인 줄만 `maxFrames`개·각 `maxFrameLength`자로 남긴다. ev.error가 없으면(교차 origin "Script error." 등)
143
+ * ev.message와 `filename:lineno:colno`를 쓴다.
144
+ * - fingerprint(kind + 숫자를 뺀 message + 첫 프레임)마다 한 번만 보낸다. 첫 발생 뒤 `flushDelayMs` 동안 같은
145
+ * fingerprint는 count로 묶고(`maxCount`까지), 보낸 뒤의 반복은 버린다.
146
+ * - 서로 다른 fingerprint는 세션당 `maxReports`건까지. 닿으면 리스너를 해제한다.
147
+ *
148
+ * URL·placement는 싣지 않는다 — 귀속은 호스트가 자기가 띄운 템플릿으로 한다. 보내기 실패는 삼킨다.
149
+ *
150
+ * **자기완결 함수다** — `bridgeRuntime`과 함께 srcdoc에 `toString()`으로 심긴다(runtime.ts 주석 참고).
151
+ * 템플릿 코드보다 먼저 돌며 `setTimeout`을 이때 잡아 둔다. 템플릿이 다른 전역을 바꿔 보고가 틀어져도
152
+ * 호스트가 형식을 다시 검사하므로(`validateReport`) 이 수집기는 신뢰 경계가 아니다.
153
+ *
154
+ * @param g 프레임 전역(`globalThis`)
155
+ * @param client 프레임 브리지 클라이언트(`bridgeRuntime` 결과)
156
+ * @param config 프로토콜 상한과 묶음 대기 시간
157
+ * @returns 이벤트 밖의 에러를 넣는 함수
158
+ */
159
+ declare function installErrorReport(g: ErrorReportGlobal, client: Pick<BridgeClient, 'request'>, config: ErrorReportConfig): ErrorReportRecord;
160
+
161
+ export { type BridgeClient, type BridgeClientConfig, type BridgeRequester, type CreateBridgeClientOptions, type DirectFetchConfig, type DirectFetchGlobal, type DirectRoute, type ErrorReportConfig, type ErrorReportGlobal, type ErrorReportRecord, type Unsubscribe, createBridgeClient, createBridgeFetch, installDirectFetch, installErrorReport };
package/dist/frame.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { g as BridgeInit, j as BridgeRequestType, i as BridgeRequestMap, e as BridgeEventMap } from './messages-CRGnyf-6.js';
1
+ import { g as BridgeInit, j as BridgeRequestType, i as BridgeRequestMap, e as BridgeEventMap, z as TemplateReportKind } from './messages-Cdjpvu2S.js';
2
2
 
3
3
  /** 프레임 런타임 설정 — srcdoc 부트스트랩이 상수로 넘긴다. */
4
4
  interface BridgeClientConfig {
@@ -73,4 +73,89 @@ declare function createBridgeClient(options?: CreateBridgeClientOptions): Bridge
73
73
  */
74
74
  declare function createBridgeFetch(client: BridgeRequester): typeof globalThis.fetch;
75
75
 
76
- export { type BridgeClient, type BridgeClientConfig, type BridgeRequester, type CreateBridgeClientOptions, type Unsubscribe, createBridgeClient, createBridgeFetch };
76
+ /** `installDirectFetch` 설정 — srcdoc 부트스트랩이 상수로 넘긴다. */
77
+ interface DirectFetchConfig {
78
+ /** 호스트 페이지 origin(srcdoc에 심긴 값). 상대 URL의 기준이자 "같은 origin" 판정 기준. */
79
+ readonly hostOrigin: string;
80
+ }
81
+ /** 프레임 fetch 라우터가 쓰는 전역의 최소 모양 — 테스트에서 가짜 창을 넘길 수 있게 한다. */
82
+ interface DirectFetchGlobal {
83
+ fetch?: typeof globalThis.fetch;
84
+ URL?: typeof URL;
85
+ Request?: typeof Request;
86
+ __bstage_fetch__?: typeof globalThis.fetch;
87
+ }
88
+ /** 갈래 판정 결과. */
89
+ type DirectRoute = 'bridge' | 'direct' | 'reject';
90
+ /**
91
+ * 격리 프레임의 fetch 라우터를(`createSrcdoc` 부트스트랩이 항상 설치한다, README "프레임 네트워크")
92
+ * `globalThis.__bstage_fetch__`에 설치한다. inline 모드용으로 쓰인 템플릿이 부르는 그 이름이다.
93
+ *
94
+ * 갈래(`new URL(input, hostOrigin)`으로 정규화한 뒤):
95
+ * - **브리지**(토큰이 필요한 목적지): hostOrigin과 같은 origin이고 경로가 init `gatewayPrefixes` 안, 또는
96
+ * origin이 init `partnerOrigins`와 정확히 일치. 기존 브리지 fetch(`client.fetch`)로 보내고 호스트가 다시 판정한다.
97
+ * - **직접**: 그 밖의 https. 부트스트랩 때 잡아 둔 프레임의 원래 `fetch`로 `credentials: 'omit'`·
98
+ * `referrerPolicy: 'no-referrer'`를 덮어 부른다. 인증 헤더는 붙이지 않는다(토큰은 프레임에 없다).
99
+ * - **거절**(`TypeError`): hostOrigin의 게이트웨이 밖 경로(혼동 방지 — 직접 보내지 않는다), https가 아닌 스킴.
100
+ *
101
+ * **자기완결 함수다** — `bridgeRuntime`과 함께 srcdoc에 `toString()`으로 심긴다(runtime.ts 주석 참고).
102
+ * 템플릿 코드보다 먼저 돌고, 판정·전송에 쓰는 전역(`fetch`·`URL`과 그 접근자·`String.prototype.slice`·
103
+ * `Promise.prototype.then`·`Object.assign`·`TypeError`)과 init의 라우팅 목록을 이때 잡아 둔다. 그래서 템플릿이
104
+ * 나중에 전역·프로토타입·init 객체를 바꿔도 갈래가 바뀌지 않는다. 갈래가 바뀌어도 토큰은 새지 않는다 —
105
+ * 브리지로 간 요청은 호스트가 따로 판정하고, 직접 간 요청에는 토큰이 없다. 이 고정은 혼동 방지다.
106
+ *
107
+ * init이 `gatewayPrefixes`를 싣지 않으면(옛 호스트) hostOrigin 요청은 경로와 무관하게 브리지로 보낸다
108
+ * (호스트가 판정하므로 안전한 쪽).
109
+ *
110
+ * @param g 프레임 전역(`globalThis`)
111
+ * @param client 프레임 브리지 클라이언트(`bridgeRuntime` 결과, frozen)
112
+ * @param config srcdoc에 심긴 hostOrigin
113
+ * @returns 설치한 fetch 라우터
114
+ */
115
+ declare function installDirectFetch(g: DirectFetchGlobal, client: Pick<BridgeClient, 'fetch' | 'ready'>, config: DirectFetchConfig): typeof globalThis.fetch;
116
+
117
+ /** `installErrorReport` 설정 — srcdoc 부트스트랩이 프로토콜 상한을 상수로 넘긴다. */
118
+ interface ErrorReportConfig {
119
+ /** 세션당 서로 다른 fingerprint 상한. 닿으면 리스너를 해제한다. */
120
+ readonly maxReports: number;
121
+ readonly maxMessageLength: number;
122
+ readonly maxFrames: number;
123
+ readonly maxFrameLength: number;
124
+ /** 같은 fingerprint 누적 횟수 상한. */
125
+ readonly maxCount: number;
126
+ /** 첫 발생 뒤 보내기까지 기다리는 시간(ms). 그 사이 같은 fingerprint는 count로 묶인다. */
127
+ readonly flushDelayMs: number;
128
+ }
129
+ /** 수집기가 쓰는 전역의 최소 모양 — 테스트에서 가짜 창을 넘길 수 있게 한다. */
130
+ interface ErrorReportGlobal {
131
+ addEventListener?(type: 'error' | 'unhandledrejection', fn: (ev: never) => void): void;
132
+ removeEventListener?(type: 'error' | 'unhandledrejection', fn: (ev: never) => void): void;
133
+ setTimeout?: (fn: () => void, ms: number) => unknown;
134
+ }
135
+ /** 수집기에 직접 넣는 함수 — 엔트리 스크립트 load 실패처럼 이벤트가 아닌 것을 보고할 때 쓴다. */
136
+ type ErrorReportRecord = (kind: TemplateReportKind, message: unknown, stack: unknown) => void;
137
+ /**
138
+ * 격리 프레임의 템플릿 에러 수집기를 설치한다(`createSrcdoc` 부트스트랩이 항상 설치한다).
139
+ *
140
+ * - 전역 `error`·`unhandledrejection`을 듣고, 반환 함수로 엔트리 load 실패(kind `load`)도 받는다.
141
+ * - 정규화: message는 문자열로 바꿔 `maxMessageLength`에서 자르고, 스택은 줄로 나눠 프레임 꼴(`at …`·`f@url`·
142
+ * `…:줄:열`)인 줄만 `maxFrames`개·각 `maxFrameLength`자로 남긴다. ev.error가 없으면(교차 origin "Script error." 등)
143
+ * ev.message와 `filename:lineno:colno`를 쓴다.
144
+ * - fingerprint(kind + 숫자를 뺀 message + 첫 프레임)마다 한 번만 보낸다. 첫 발생 뒤 `flushDelayMs` 동안 같은
145
+ * fingerprint는 count로 묶고(`maxCount`까지), 보낸 뒤의 반복은 버린다.
146
+ * - 서로 다른 fingerprint는 세션당 `maxReports`건까지. 닿으면 리스너를 해제한다.
147
+ *
148
+ * URL·placement는 싣지 않는다 — 귀속은 호스트가 자기가 띄운 템플릿으로 한다. 보내기 실패는 삼킨다.
149
+ *
150
+ * **자기완결 함수다** — `bridgeRuntime`과 함께 srcdoc에 `toString()`으로 심긴다(runtime.ts 주석 참고).
151
+ * 템플릿 코드보다 먼저 돌며 `setTimeout`을 이때 잡아 둔다. 템플릿이 다른 전역을 바꿔 보고가 틀어져도
152
+ * 호스트가 형식을 다시 검사하므로(`validateReport`) 이 수집기는 신뢰 경계가 아니다.
153
+ *
154
+ * @param g 프레임 전역(`globalThis`)
155
+ * @param client 프레임 브리지 클라이언트(`bridgeRuntime` 결과)
156
+ * @param config 프로토콜 상한과 묶음 대기 시간
157
+ * @returns 이벤트 밖의 에러를 넣는 함수
158
+ */
159
+ declare function installErrorReport(g: ErrorReportGlobal, client: Pick<BridgeClient, 'request'>, config: ErrorReportConfig): ErrorReportRecord;
160
+
161
+ export { type BridgeClient, type BridgeClientConfig, type BridgeRequester, type CreateBridgeClientOptions, type DirectFetchConfig, type DirectFetchGlobal, type DirectRoute, type ErrorReportConfig, type ErrorReportGlobal, type ErrorReportRecord, type Unsubscribe, createBridgeClient, createBridgeFetch, installDirectFetch, installErrorReport };
package/dist/frame.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  bridgeRuntime,
3
- createBridgeFetch
4
- } from "./chunk-N3JID7KQ.js";
3
+ createBridgeFetch,
4
+ installDirectFetch,
5
+ installErrorReport
6
+ } from "./chunk-TPKNGW34.js";
5
7
 
6
8
  // src/frame/createBridgeClient.ts
7
9
  var BRIDGE_CONFIG_GLOBAL = "__bstage_bridge_config__";
@@ -17,5 +19,7 @@ function createBridgeClient(options = {}) {
17
19
  }
18
20
  export {
19
21
  createBridgeClient,
20
- createBridgeFetch
22
+ createBridgeFetch,
23
+ installDirectFetch,
24
+ installErrorReport
21
25
  };