@bstage-sdk/bridge 0.0.0-stage → 0.9.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/LICENSE +21 -0
- package/README.md +194 -2
- package/dist/chunk-6LV7HEN4.js +583 -0
- package/dist/chunk-KQMJY4ZH.js +266 -0
- package/dist/createSrcdoc-bOoxttPz.d.cts +52 -0
- package/dist/createSrcdoc-bOoxttPz.d.ts +52 -0
- package/dist/frame.cjs +306 -0
- package/dist/frame.d.cts +72 -0
- package/dist/frame.d.ts +72 -0
- package/dist/frame.js +21 -0
- package/dist/host.cjs +1399 -0
- package/dist/host.d.cts +262 -0
- package/dist/host.d.ts +262 -0
- package/dist/host.js +594 -0
- package/dist/index.cjs +673 -0
- package/dist/index.d.cts +32 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +63 -0
- package/dist/messages-9N0GFq5b.d.cts +186 -0
- package/dist/messages-9N0GFq5b.d.ts +186 -0
- package/package.json +68 -3
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
BRIDGE_ERROR_CODES: () => BRIDGE_ERROR_CODES,
|
|
24
|
+
BRIDGE_NAMESPACE: () => BRIDGE_NAMESPACE,
|
|
25
|
+
BRIDGE_SURFACES: () => BRIDGE_SURFACES,
|
|
26
|
+
BRIDGE_VERSION: () => BRIDGE_VERSION,
|
|
27
|
+
BridgeError: () => BridgeError,
|
|
28
|
+
HOST_EVENT_TYPES: () => HOST_EVENT_TYPES,
|
|
29
|
+
IFRAME_ATTRIBUTES: () => IFRAME_ATTRIBUTES,
|
|
30
|
+
INIT_RESPONSE: () => INIT_RESPONSE,
|
|
31
|
+
MAX_ELEMENT_NAME_LENGTH: () => MAX_ELEMENT_NAME_LENGTH,
|
|
32
|
+
MAX_ENVELOPE_ID_LENGTH: () => MAX_ENVELOPE_ID_LENGTH,
|
|
33
|
+
MAX_ENVELOPE_TYPE_LENGTH: () => MAX_ENVELOPE_TYPE_LENGTH,
|
|
34
|
+
MAX_MEDIA_ORIGINS: () => MAX_MEDIA_ORIGINS,
|
|
35
|
+
READY_EVENT: () => READY_EVENT,
|
|
36
|
+
REQUEST_TIMEOUT_MS: () => REQUEST_TIMEOUT_MS,
|
|
37
|
+
REQUEST_TYPES: () => REQUEST_TYPES,
|
|
38
|
+
buildFrameCsp: () => buildFrameCsp,
|
|
39
|
+
createEnvelope: () => createEnvelope,
|
|
40
|
+
createNonce: () => createNonce,
|
|
41
|
+
createSession: () => createSession,
|
|
42
|
+
createSrcdoc: () => createSrcdoc,
|
|
43
|
+
hasSession: () => hasSession,
|
|
44
|
+
isBridgeEnvelope: () => isBridgeEnvelope,
|
|
45
|
+
isBridgeErrorCode: () => isBridgeErrorCode,
|
|
46
|
+
isBridgeErrorLike: () => isBridgeErrorLike,
|
|
47
|
+
isBridgeSurface: () => isBridgeSurface,
|
|
48
|
+
isHostEventType: () => isHostEventType,
|
|
49
|
+
isRequestType: () => isRequestType,
|
|
50
|
+
isValidNonce: () => isValidNonce,
|
|
51
|
+
isValidSession: () => isValidSession
|
|
52
|
+
});
|
|
53
|
+
module.exports = __toCommonJS(src_exports);
|
|
54
|
+
|
|
55
|
+
// src/protocol/envelope.ts
|
|
56
|
+
var BRIDGE_VERSION = 1;
|
|
57
|
+
var BRIDGE_NAMESPACE = "bstage-bridge";
|
|
58
|
+
var MAX_ENVELOPE_ID_LENGTH = 64;
|
|
59
|
+
var MAX_ENVELOPE_TYPE_LENGTH = 32;
|
|
60
|
+
var ENVELOPE_KINDS = ["req", "res", "evt"];
|
|
61
|
+
function isNonEmptyString(value, max) {
|
|
62
|
+
return typeof value === "string" && value.length > 0 && value.length <= max;
|
|
63
|
+
}
|
|
64
|
+
function isBridgeEnvelope(value) {
|
|
65
|
+
if (typeof value !== "object" || value === null) return false;
|
|
66
|
+
const env = value;
|
|
67
|
+
return env.v === BRIDGE_VERSION && env.ns === BRIDGE_NAMESPACE && isNonEmptyString(env.id, MAX_ENVELOPE_ID_LENGTH) && ENVELOPE_KINDS.includes(env.kind) && isNonEmptyString(env.type, MAX_ENVELOPE_TYPE_LENGTH) && "payload" in env;
|
|
68
|
+
}
|
|
69
|
+
function createEnvelope(kind, type, id, payload) {
|
|
70
|
+
return { v: BRIDGE_VERSION, ns: BRIDGE_NAMESPACE, id, kind, type, payload };
|
|
71
|
+
}
|
|
72
|
+
function hasSession(payload, session) {
|
|
73
|
+
return typeof payload === "object" && payload !== null && payload.session === session;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/protocol/errors.ts
|
|
77
|
+
var BRIDGE_ERROR_CODES = [
|
|
78
|
+
"FORBIDDEN_DESTINATION",
|
|
79
|
+
"FORBIDDEN_METHOD",
|
|
80
|
+
"BODY_TOO_LARGE",
|
|
81
|
+
"RATE_LIMITED",
|
|
82
|
+
"BAD_REQUEST",
|
|
83
|
+
"TIMEOUT",
|
|
84
|
+
"NETWORK_ERROR",
|
|
85
|
+
"UNSUPPORTED"
|
|
86
|
+
];
|
|
87
|
+
var BridgeError = class extends Error {
|
|
88
|
+
constructor(code, message) {
|
|
89
|
+
super(message);
|
|
90
|
+
this.name = "BridgeError";
|
|
91
|
+
this.code = code;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
function isBridgeErrorCode(value) {
|
|
95
|
+
return BRIDGE_ERROR_CODES.includes(value);
|
|
96
|
+
}
|
|
97
|
+
function isBridgeErrorLike(value) {
|
|
98
|
+
return value instanceof Error && isBridgeErrorCode(value.code);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/protocol/messages.ts
|
|
102
|
+
var REQUEST_TIMEOUT_MS = 15e3;
|
|
103
|
+
var REQUEST_TYPES = [
|
|
104
|
+
"fetch",
|
|
105
|
+
"navigate",
|
|
106
|
+
"back",
|
|
107
|
+
"openExternal",
|
|
108
|
+
"toast",
|
|
109
|
+
"resize",
|
|
110
|
+
"track",
|
|
111
|
+
"storage",
|
|
112
|
+
"log"
|
|
113
|
+
];
|
|
114
|
+
var HOST_EVENT_TYPES = ["slot.context", "locale", "member", "theme", "visibility"];
|
|
115
|
+
var READY_EVENT = "ready";
|
|
116
|
+
var INIT_RESPONSE = "init";
|
|
117
|
+
var BRIDGE_SURFACES = ["page", "slot"];
|
|
118
|
+
function isBridgeSurface(value) {
|
|
119
|
+
return BRIDGE_SURFACES.includes(value);
|
|
120
|
+
}
|
|
121
|
+
var IFRAME_ATTRIBUTES = {
|
|
122
|
+
sandbox: "allow-scripts",
|
|
123
|
+
allow: "autoplay; fullscreen; picture-in-picture; encrypted-media; clipboard-write",
|
|
124
|
+
referrerpolicy: "no-referrer"
|
|
125
|
+
};
|
|
126
|
+
function isRequestType(value) {
|
|
127
|
+
return REQUEST_TYPES.includes(value);
|
|
128
|
+
}
|
|
129
|
+
function isHostEventType(value) {
|
|
130
|
+
return HOST_EVENT_TYPES.includes(value);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/protocol/session.ts
|
|
134
|
+
var SESSION_RE = /^[0-9a-f]{32}$/;
|
|
135
|
+
var NONCE_RE = /^[A-Za-z0-9+/_-]{22,}={0,2}$/;
|
|
136
|
+
function randomBytes(length) {
|
|
137
|
+
const bytes = new Uint8Array(length);
|
|
138
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
139
|
+
return bytes;
|
|
140
|
+
}
|
|
141
|
+
function createSession() {
|
|
142
|
+
return Array.from(randomBytes(16), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
143
|
+
}
|
|
144
|
+
function isValidSession(value) {
|
|
145
|
+
return typeof value === "string" && SESSION_RE.test(value);
|
|
146
|
+
}
|
|
147
|
+
function createNonce() {
|
|
148
|
+
const binary = String.fromCharCode(...randomBytes(16));
|
|
149
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
150
|
+
}
|
|
151
|
+
function isValidNonce(value) {
|
|
152
|
+
return typeof value === "string" && NONCE_RE.test(value);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/frame/runtime.ts
|
|
156
|
+
function bridgeRuntime(g, makeFetch, config) {
|
|
157
|
+
const NS = "bstage-bridge";
|
|
158
|
+
const TIMEOUT_MS = 15e3;
|
|
159
|
+
const READY_INTERVAL_MS = 250;
|
|
160
|
+
const READY_FAST_ATTEMPTS = 40;
|
|
161
|
+
const READY_SLOW_INTERVAL_MS = 2e3;
|
|
162
|
+
const MAX_ID = 64;
|
|
163
|
+
const MAX_TYPE = 32;
|
|
164
|
+
if (g.__bstage_bridge__) return g.__bstage_bridge__;
|
|
165
|
+
const session = config.session;
|
|
166
|
+
const hostOrigin = config.hostOrigin;
|
|
167
|
+
const parent = g.parent;
|
|
168
|
+
const pending = /* @__PURE__ */ new Map();
|
|
169
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
170
|
+
let init;
|
|
171
|
+
let seq = 0;
|
|
172
|
+
function randomPrefix() {
|
|
173
|
+
const bytes = new Uint8Array(8);
|
|
174
|
+
if (g.crypto && typeof g.crypto.getRandomValues === "function") {
|
|
175
|
+
g.crypto.getRandomValues(bytes);
|
|
176
|
+
} else {
|
|
177
|
+
for (let i = 0; i < bytes.length; i += 1) bytes[i] = Math.floor(Math.random() * 256);
|
|
178
|
+
}
|
|
179
|
+
let out = "";
|
|
180
|
+
for (let i = 0; i < bytes.length; i += 1) out += (bytes[i] + 256).toString(16).slice(1);
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
183
|
+
const idPrefix = randomPrefix();
|
|
184
|
+
function nextId() {
|
|
185
|
+
seq += 1;
|
|
186
|
+
return idPrefix + "-" + seq;
|
|
187
|
+
}
|
|
188
|
+
function codeError(code, message) {
|
|
189
|
+
const err = new Error(message);
|
|
190
|
+
err.name = "BridgeError";
|
|
191
|
+
err.code = code;
|
|
192
|
+
return err;
|
|
193
|
+
}
|
|
194
|
+
function post(kind, type, id, payload) {
|
|
195
|
+
if (!parent) return;
|
|
196
|
+
const body = Object.assign({}, payload, { session });
|
|
197
|
+
parent.postMessage({ v: 1, ns: NS, id, kind, type, payload: body }, hostOrigin);
|
|
198
|
+
}
|
|
199
|
+
function isEnvelope(m) {
|
|
200
|
+
if (typeof m !== "object" || m === null) return false;
|
|
201
|
+
const e = m;
|
|
202
|
+
return e.v === 1 && e.ns === NS && typeof e.id === "string" && e.id.length > 0 && e.id.length <= MAX_ID && (e.kind === "req" || e.kind === "res" || e.kind === "evt") && typeof e.type === "string" && e.type.length > 0 && e.type.length <= MAX_TYPE && "payload" in e;
|
|
203
|
+
}
|
|
204
|
+
let resolveReady = function() {
|
|
205
|
+
};
|
|
206
|
+
const ready = new Promise(function(resolve) {
|
|
207
|
+
resolveReady = resolve;
|
|
208
|
+
});
|
|
209
|
+
function emit(type, payload) {
|
|
210
|
+
const fns = listeners.get(type);
|
|
211
|
+
if (!fns) return;
|
|
212
|
+
fns.slice().forEach(function(fn) {
|
|
213
|
+
try {
|
|
214
|
+
fn(payload);
|
|
215
|
+
} catch {
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
function applyEvent(type, payload) {
|
|
220
|
+
if (!init || typeof payload !== "object" || payload === null) return;
|
|
221
|
+
const p = payload;
|
|
222
|
+
if (type === "locale" && typeof p.locale === "string") {
|
|
223
|
+
init = Object.assign({}, init, { locale: p.locale, translations: p.translations });
|
|
224
|
+
} else if (type === "member" && typeof p.signedIn === "boolean") {
|
|
225
|
+
init = Object.assign({}, init, { member: payload });
|
|
226
|
+
} else if (type === "theme" && typeof p.dark === "boolean") {
|
|
227
|
+
init = Object.assign({}, init, { theme: payload });
|
|
228
|
+
} else if (type === "slot.context" && typeof p.id === "string") {
|
|
229
|
+
init = Object.assign({}, init, { slot: payload });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function onMessage(ev) {
|
|
233
|
+
if (!parent || ev.source !== parent) return;
|
|
234
|
+
if (ev.origin !== hostOrigin) return;
|
|
235
|
+
const m = ev.data;
|
|
236
|
+
if (!isEnvelope(m)) return;
|
|
237
|
+
const result = m.payload;
|
|
238
|
+
if (m.kind === "res" && m.type === "init") {
|
|
239
|
+
if (!init && result && result.ok === true && result.data) {
|
|
240
|
+
init = result.data;
|
|
241
|
+
clearInterval(readyTimer);
|
|
242
|
+
resolveReady(init);
|
|
243
|
+
}
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (m.kind === "res") {
|
|
247
|
+
const entry = pending.get(m.id);
|
|
248
|
+
if (!entry) return;
|
|
249
|
+
pending.delete(m.id);
|
|
250
|
+
clearTimeout(entry.timer);
|
|
251
|
+
if (result && result.ok === true) {
|
|
252
|
+
entry.resolve(result.data);
|
|
253
|
+
} else {
|
|
254
|
+
const error = result && result.error || {};
|
|
255
|
+
entry.reject(
|
|
256
|
+
codeError(
|
|
257
|
+
typeof error.code === "string" ? error.code : "BAD_REQUEST",
|
|
258
|
+
typeof error.message === "string" ? error.message : "bridge request failed"
|
|
259
|
+
)
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (m.kind === "evt") {
|
|
265
|
+
applyEvent(m.type, m.payload);
|
|
266
|
+
emit(m.type, m.payload);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
g.addEventListener("message", onMessage);
|
|
270
|
+
let readyAttempts = 0;
|
|
271
|
+
function sendReady() {
|
|
272
|
+
readyAttempts += 1;
|
|
273
|
+
if (readyAttempts === READY_FAST_ATTEMPTS) {
|
|
274
|
+
clearInterval(readyTimer);
|
|
275
|
+
readyTimer = setInterval(sendReady, READY_SLOW_INTERVAL_MS);
|
|
276
|
+
}
|
|
277
|
+
post("evt", "ready", nextId(), {});
|
|
278
|
+
}
|
|
279
|
+
let readyTimer = setInterval(sendReady, READY_INTERVAL_MS);
|
|
280
|
+
sendReady();
|
|
281
|
+
function request(type, payload) {
|
|
282
|
+
return new Promise(function(resolve, reject) {
|
|
283
|
+
const id = nextId();
|
|
284
|
+
const timer = setTimeout(function() {
|
|
285
|
+
pending.delete(id);
|
|
286
|
+
reject(codeError("TIMEOUT", "bridge request timed out: " + type));
|
|
287
|
+
}, TIMEOUT_MS);
|
|
288
|
+
pending.set(id, { resolve, reject, timer });
|
|
289
|
+
post("req", type, id, payload || {});
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
function on(type, fn) {
|
|
293
|
+
const current = listeners.get(type) || [];
|
|
294
|
+
listeners.set(type, current.concat([fn]));
|
|
295
|
+
return function() {
|
|
296
|
+
const now = listeners.get(type) || [];
|
|
297
|
+
listeners.set(
|
|
298
|
+
type,
|
|
299
|
+
now.filter(function(f) {
|
|
300
|
+
return f !== fn;
|
|
301
|
+
})
|
|
302
|
+
);
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
const base = {
|
|
306
|
+
version: 1,
|
|
307
|
+
hostOrigin,
|
|
308
|
+
ready,
|
|
309
|
+
getInit: function() {
|
|
310
|
+
return init;
|
|
311
|
+
},
|
|
312
|
+
request,
|
|
313
|
+
on
|
|
314
|
+
};
|
|
315
|
+
const client = Object.freeze(Object.assign({}, base, { fetch: makeFetch(base) }));
|
|
316
|
+
Object.defineProperty(g, "__bstage_bridge__", {
|
|
317
|
+
value: client,
|
|
318
|
+
writable: false,
|
|
319
|
+
configurable: false,
|
|
320
|
+
enumerable: false
|
|
321
|
+
});
|
|
322
|
+
return client;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// src/frame/createBridgeFetch.ts
|
|
326
|
+
function createBridgeFetch(client) {
|
|
327
|
+
const NULL_BODY_STATUS = [204, 205, 304];
|
|
328
|
+
function toHeaderRecord(h) {
|
|
329
|
+
const out = {};
|
|
330
|
+
if (!h) return out;
|
|
331
|
+
new Headers(h).forEach(function(value, key) {
|
|
332
|
+
out[key] = value;
|
|
333
|
+
});
|
|
334
|
+
return out;
|
|
335
|
+
}
|
|
336
|
+
function abortError(signal) {
|
|
337
|
+
if (signal.reason !== void 0) return signal.reason;
|
|
338
|
+
return new DOMException("The operation was aborted.", "AbortError");
|
|
339
|
+
}
|
|
340
|
+
function bridgeFetch(input, init) {
|
|
341
|
+
return new Promise(function(resolve, reject) {
|
|
342
|
+
const opts = init || {};
|
|
343
|
+
const isRequest = typeof input === "object" && input !== null && typeof input.method === "string";
|
|
344
|
+
const url = isRequest ? input.url : String(input);
|
|
345
|
+
const method = String(opts.method || (isRequest ? input.method : "GET"));
|
|
346
|
+
const headers = toHeaderRecord(
|
|
347
|
+
opts.headers || (isRequest ? input.headers : null)
|
|
348
|
+
);
|
|
349
|
+
let body = null;
|
|
350
|
+
if (isRequest && input.body !== null && opts.body == null) {
|
|
351
|
+
reject(new TypeError("bridge fetch: pass the body via init.body"));
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const raw = opts.body;
|
|
355
|
+
if (raw === void 0 || raw === null) {
|
|
356
|
+
} else if (typeof raw === "string") {
|
|
357
|
+
body = raw;
|
|
358
|
+
} else if (typeof URLSearchParams !== "undefined" && raw instanceof URLSearchParams) {
|
|
359
|
+
body = raw.toString();
|
|
360
|
+
if (!headers["content-type"]) {
|
|
361
|
+
headers["content-type"] = "application/x-www-form-urlencoded;charset=UTF-8";
|
|
362
|
+
}
|
|
363
|
+
} else {
|
|
364
|
+
reject(new TypeError("bridge fetch: unsupported body type"));
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const signal = opts.signal;
|
|
368
|
+
if (signal && signal.aborted) {
|
|
369
|
+
reject(abortError(signal));
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
let settled = false;
|
|
373
|
+
function onAbort() {
|
|
374
|
+
if (settled || !signal) return;
|
|
375
|
+
settled = true;
|
|
376
|
+
reject(abortError(signal));
|
|
377
|
+
}
|
|
378
|
+
if (signal) signal.addEventListener("abort", onAbort);
|
|
379
|
+
const payload = { url, method, headers, body };
|
|
380
|
+
client.request("fetch", payload).then(
|
|
381
|
+
function(res) {
|
|
382
|
+
if (signal) signal.removeEventListener("abort", onAbort);
|
|
383
|
+
if (settled) return;
|
|
384
|
+
settled = true;
|
|
385
|
+
try {
|
|
386
|
+
const nullBody = method.toUpperCase() === "HEAD" || NULL_BODY_STATUS.indexOf(res.status) >= 0;
|
|
387
|
+
const responseHeaders = {};
|
|
388
|
+
if (res.headers && typeof res.headers["content-type"] === "string") {
|
|
389
|
+
responseHeaders["content-type"] = res.headers["content-type"];
|
|
390
|
+
}
|
|
391
|
+
resolve(
|
|
392
|
+
new Response(nullBody ? null : typeof res.bodyText === "string" ? res.bodyText : "", {
|
|
393
|
+
status: res.status,
|
|
394
|
+
statusText: res.statusText,
|
|
395
|
+
headers: responseHeaders
|
|
396
|
+
})
|
|
397
|
+
);
|
|
398
|
+
} catch {
|
|
399
|
+
reject(new TypeError("bridge fetch: invalid response"));
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
function(err) {
|
|
403
|
+
if (signal) signal.removeEventListener("abort", onAbort);
|
|
404
|
+
if (settled) return;
|
|
405
|
+
settled = true;
|
|
406
|
+
const code = err && typeof err === "object" ? err.code : void 0;
|
|
407
|
+
const failure = new TypeError("bridge fetch failed: " + String(code || "NETWORK_ERROR"));
|
|
408
|
+
failure.code = code || "NETWORK_ERROR";
|
|
409
|
+
reject(failure);
|
|
410
|
+
}
|
|
411
|
+
);
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
return bridgeFetch;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// src/srcdoc/escape.ts
|
|
418
|
+
var HTML_ESCAPES = {
|
|
419
|
+
"&": "&",
|
|
420
|
+
"<": "<",
|
|
421
|
+
">": ">",
|
|
422
|
+
'"': """,
|
|
423
|
+
"'": "'"
|
|
424
|
+
};
|
|
425
|
+
function escapeHtml(value) {
|
|
426
|
+
return value.replace(/[&<>"']/g, (ch) => HTML_ESCAPES[ch]);
|
|
427
|
+
}
|
|
428
|
+
function toScriptLiteral(value) {
|
|
429
|
+
return JSON.stringify(value).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// src/host/pathSafety.ts
|
|
433
|
+
var REJECT = Object.freeze({ ok: false, forms: Object.freeze([]) });
|
|
434
|
+
|
|
435
|
+
// src/host/policy.ts
|
|
436
|
+
var DEFAULT_GATEWAY_DENY_SEGMENTS = Object.freeze([
|
|
437
|
+
"auth",
|
|
438
|
+
"logout",
|
|
439
|
+
"mfa",
|
|
440
|
+
"token",
|
|
441
|
+
"oauth",
|
|
442
|
+
"sso",
|
|
443
|
+
"debug",
|
|
444
|
+
"debugger",
|
|
445
|
+
"xdebug",
|
|
446
|
+
"withdraw"
|
|
447
|
+
]);
|
|
448
|
+
var ALWAYS_DENIED_SEGMENT_SUBSTRINGS = Object.freeze(["token"]);
|
|
449
|
+
var DEFAULT_POLICY = Object.freeze({
|
|
450
|
+
gatewayPrefixes: Object.freeze(["/gw/"]),
|
|
451
|
+
gatewayDenyPrefixes: Object.freeze(["/gw/account/v1/auth/", "/gw/account/v1/users/logout"]),
|
|
452
|
+
gatewayDenySegments: DEFAULT_GATEWAY_DENY_SEGMENTS,
|
|
453
|
+
allowExternal: false,
|
|
454
|
+
maxBodyBytes: 1024 * 1024,
|
|
455
|
+
maxRequestsPerMinute: 300,
|
|
456
|
+
maxResponseBytes: 10 * 1024 * 1024
|
|
457
|
+
});
|
|
458
|
+
var ALLOWED_REQUEST_HEADERS = Object.freeze([
|
|
459
|
+
"content-type",
|
|
460
|
+
"accept",
|
|
461
|
+
"accept-language",
|
|
462
|
+
// BstageClient가 보내는 앱 자격 헤더(파트너 콘솔 발급값, 브라우저 노출 전제 — 회원 토큰이 아니다).
|
|
463
|
+
// 호스트가 `pinnedHeaders`로 값을 주면 그 값이 이긴다.
|
|
464
|
+
"x-bstage-app-id",
|
|
465
|
+
"x-bstage-app-key",
|
|
466
|
+
// BstageClient가 모든 요청에 싣는 core 버전(정보용, 비밀 아님).
|
|
467
|
+
"x-bmf-sdk-version"
|
|
468
|
+
]);
|
|
469
|
+
var HOST_ONLY_HEADERS = Object.freeze([
|
|
470
|
+
"x-bstage-tenant-id",
|
|
471
|
+
"x-bstage-org-id",
|
|
472
|
+
"x-bstage-enterprise-id"
|
|
473
|
+
]);
|
|
474
|
+
var ALLOWED_METHODS = Object.freeze([
|
|
475
|
+
"GET",
|
|
476
|
+
"HEAD",
|
|
477
|
+
"POST",
|
|
478
|
+
"PUT",
|
|
479
|
+
"PATCH",
|
|
480
|
+
"DELETE"
|
|
481
|
+
]);
|
|
482
|
+
var HOSTNAME_RE = /^(?:[a-z0-9-]+(?:\.[a-z0-9-]+)*|\[[0-9a-f:.]+\])$/;
|
|
483
|
+
function normalizeExactOrigin(value, protocols) {
|
|
484
|
+
if (typeof value !== "string") return void 0;
|
|
485
|
+
try {
|
|
486
|
+
const url = new URL(value);
|
|
487
|
+
if (!protocols.includes(url.protocol) || !HOSTNAME_RE.test(url.hostname)) return void 0;
|
|
488
|
+
return url.origin === value ? value : void 0;
|
|
489
|
+
} catch {
|
|
490
|
+
return void 0;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
var LOCAL_HOST_NAMES = Object.freeze([
|
|
494
|
+
"localhost",
|
|
495
|
+
"local",
|
|
496
|
+
"internal",
|
|
497
|
+
"home.arpa",
|
|
498
|
+
"test",
|
|
499
|
+
"invalid",
|
|
500
|
+
"example",
|
|
501
|
+
"lan",
|
|
502
|
+
"corp",
|
|
503
|
+
"intranet",
|
|
504
|
+
"localdomain"
|
|
505
|
+
]);
|
|
506
|
+
|
|
507
|
+
// src/srcdoc/createSrcdoc.ts
|
|
508
|
+
var MAX_MEDIA_ORIGINS = 20;
|
|
509
|
+
var MAX_ELEMENT_NAME_LENGTH = 100;
|
|
510
|
+
var ELEMENT_NAME_RE = /^[a-z][a-z0-9._]*-[a-z0-9._-]*$/;
|
|
511
|
+
function isValidElementName(value) {
|
|
512
|
+
return typeof value === "string" && value.length <= MAX_ELEMENT_NAME_LENGTH && ELEMENT_NAME_RE.test(value);
|
|
513
|
+
}
|
|
514
|
+
function assertOrigin(label, value) {
|
|
515
|
+
const origin = normalizeExactOrigin(value, ["https:", "http:"]);
|
|
516
|
+
if (!origin) throw new TypeError(`[bstage-bridge] ${label}\uB294 \uC815\uD655\uD55C origin\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4`);
|
|
517
|
+
return origin;
|
|
518
|
+
}
|
|
519
|
+
function assertEntryUrl(entryUrl, assetOrigin) {
|
|
520
|
+
let parsed;
|
|
521
|
+
try {
|
|
522
|
+
parsed = new URL(entryUrl);
|
|
523
|
+
} catch {
|
|
524
|
+
throw new TypeError("[bstage-bridge] entryUrl\uC774 URL\uC774 \uC544\uB2D9\uB2C8\uB2E4");
|
|
525
|
+
}
|
|
526
|
+
if (parsed.origin !== assetOrigin || parsed.username || parsed.password) {
|
|
527
|
+
throw new TypeError("[bstage-bridge] entryUrl\uC740 assetOrigin\uC5D0 \uC788\uC5B4\uC57C \uD569\uB2C8\uB2E4");
|
|
528
|
+
}
|
|
529
|
+
return parsed.href;
|
|
530
|
+
}
|
|
531
|
+
function normalizeMediaOrigins(value) {
|
|
532
|
+
if (value === void 0) return Object.freeze([]);
|
|
533
|
+
if (!Array.isArray(value)) {
|
|
534
|
+
throw new TypeError("[bstage-bridge] mediaOrigins\uB294 \uBC30\uC5F4\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4");
|
|
535
|
+
}
|
|
536
|
+
if (value.length > MAX_MEDIA_ORIGINS) {
|
|
537
|
+
throw new TypeError(`[bstage-bridge] mediaOrigins\uB294 \uCD5C\uB300 ${MAX_MEDIA_ORIGINS}\uAC1C\uC785\uB2C8\uB2E4`);
|
|
538
|
+
}
|
|
539
|
+
const origins = value.map((o) => {
|
|
540
|
+
const origin = normalizeExactOrigin(o, ["https:"]);
|
|
541
|
+
if (!origin) {
|
|
542
|
+
throw new TypeError("[bstage-bridge] mediaOrigins \uD56D\uBAA9\uC740 \uC815\uD655\uD55C https origin\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4");
|
|
543
|
+
}
|
|
544
|
+
return origin;
|
|
545
|
+
});
|
|
546
|
+
return Object.freeze([...new Set(origins)]);
|
|
547
|
+
}
|
|
548
|
+
function buildFrameCsp(nonce, assetOrigin, mediaOrigins = []) {
|
|
549
|
+
const media = [assetOrigin, ...normalizeMediaOrigins(mediaOrigins)].join(" ");
|
|
550
|
+
return [
|
|
551
|
+
"default-src 'none'",
|
|
552
|
+
`script-src 'nonce-${nonce}' ${assetOrigin}`,
|
|
553
|
+
`style-src 'unsafe-inline' ${assetOrigin}`,
|
|
554
|
+
`img-src ${media} data: blob:`,
|
|
555
|
+
`font-src ${assetOrigin} data:`,
|
|
556
|
+
`media-src ${media} blob:`,
|
|
557
|
+
"connect-src 'none'",
|
|
558
|
+
"frame-src 'none'",
|
|
559
|
+
"form-action 'none'",
|
|
560
|
+
"base-uri 'none'"
|
|
561
|
+
].join("; ");
|
|
562
|
+
}
|
|
563
|
+
function bootstrapScript(o) {
|
|
564
|
+
return [
|
|
565
|
+
"(function () {",
|
|
566
|
+
'"use strict";',
|
|
567
|
+
`var SESSION = ${toScriptLiteral(o.session)};`,
|
|
568
|
+
`var HOST_ORIGIN = ${toScriptLiteral(o.hostOrigin)};`,
|
|
569
|
+
`var ENTRY_URL = ${toScriptLiteral(o.entryUrl)};`,
|
|
570
|
+
`var ELEMENT_NAME = ${toScriptLiteral(o.elementName)};`,
|
|
571
|
+
`var bridgeRuntime = ${bridgeRuntime.toString()};`,
|
|
572
|
+
`var createBridgeFetch = ${createBridgeFetch.toString()};`,
|
|
573
|
+
"var client = bridgeRuntime(globalThis, createBridgeFetch, { session: SESSION, hostOrigin: HOST_ORIGIN });",
|
|
574
|
+
"var lastHeight = -1;",
|
|
575
|
+
"var scheduled = false;",
|
|
576
|
+
"function reportHeight() {",
|
|
577
|
+
" scheduled = false;",
|
|
578
|
+
" var h = Math.ceil(document.body.getBoundingClientRect().height);",
|
|
579
|
+
" if (h === lastHeight) return;",
|
|
580
|
+
" lastHeight = h;",
|
|
581
|
+
" client.request('resize', { height: h }).catch(function () {});",
|
|
582
|
+
"}",
|
|
583
|
+
"function scheduleHeight() {",
|
|
584
|
+
" if (scheduled) return;",
|
|
585
|
+
" scheduled = true;",
|
|
586
|
+
" requestAnimationFrame(reportHeight);",
|
|
587
|
+
"}",
|
|
588
|
+
"client.ready.then(function () {",
|
|
589
|
+
" var script = document.createElement('script');",
|
|
590
|
+
" script.src = ENTRY_URL;",
|
|
591
|
+
" script.onerror = function () {",
|
|
592
|
+
" client.request('log', { level: 'error', message: 'template entry failed to load' }).catch(function () {});",
|
|
593
|
+
" };",
|
|
594
|
+
" document.head.appendChild(script);",
|
|
595
|
+
" customElements.whenDefined(ELEMENT_NAME).then(function () {",
|
|
596
|
+
" document.body.appendChild(document.createElement(ELEMENT_NAME));",
|
|
597
|
+
" if (typeof ResizeObserver === 'function') {",
|
|
598
|
+
" new ResizeObserver(scheduleHeight).observe(document.body);",
|
|
599
|
+
" }",
|
|
600
|
+
" scheduleHeight();",
|
|
601
|
+
" });",
|
|
602
|
+
"}, function () {});",
|
|
603
|
+
"})();"
|
|
604
|
+
].join("\n");
|
|
605
|
+
}
|
|
606
|
+
function createSrcdoc(options) {
|
|
607
|
+
const { nonce, session, title, elementName } = options;
|
|
608
|
+
if (!isValidNonce(nonce)) throw new TypeError("[bstage-bridge] nonce \uD615\uC2DD\uC774 \uC798\uBABB\uB410\uC2B5\uB2C8\uB2E4");
|
|
609
|
+
if (!isValidSession(session)) throw new TypeError("[bstage-bridge] session \uD615\uC2DD\uC774 \uC798\uBABB\uB410\uC2B5\uB2C8\uB2E4");
|
|
610
|
+
const hostOrigin = assertOrigin("hostOrigin", options.hostOrigin);
|
|
611
|
+
const assetOrigin = assertOrigin("assetOrigin", options.assetOrigin);
|
|
612
|
+
const entryUrl = assertEntryUrl(options.entryUrl, assetOrigin);
|
|
613
|
+
if (!isValidElementName(elementName)) {
|
|
614
|
+
throw new TypeError("[bstage-bridge] elementName\uC774 custom element \uC774\uB984\uC774 \uC544\uB2D9\uB2C8\uB2E4");
|
|
615
|
+
}
|
|
616
|
+
if (typeof title !== "string") throw new TypeError("[bstage-bridge] title\uC740 \uBB38\uC790\uC5F4\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4");
|
|
617
|
+
const mediaOrigins = normalizeMediaOrigins(options.mediaOrigins);
|
|
618
|
+
const csp = buildFrameCsp(nonce, assetOrigin, mediaOrigins);
|
|
619
|
+
const script = bootstrapScript({ session, hostOrigin, entryUrl, elementName });
|
|
620
|
+
if (/<\/script|<!--/i.test(script)) {
|
|
621
|
+
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");
|
|
622
|
+
}
|
|
623
|
+
return [
|
|
624
|
+
"<!doctype html>",
|
|
625
|
+
"<html>",
|
|
626
|
+
"<head>",
|
|
627
|
+
'<meta charset="utf-8">',
|
|
628
|
+
`<meta http-equiv="Content-Security-Policy" content="${escapeHtml(csp)}">`,
|
|
629
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
630
|
+
`<title>${escapeHtml(title)}</title>`,
|
|
631
|
+
// body 높이를 콘텐츠 높이로 잰다 — documentElement.scrollHeight는 뷰포트 이상이라 줄어들지 못한다.
|
|
632
|
+
// flow-root는 자식 margin이 body 밖으로 접혀 나가 높이에서 빠지는 것을 막는다.
|
|
633
|
+
"<style>html,body{margin:0;padding:0;background:transparent;height:auto}body{display:flow-root}</style>",
|
|
634
|
+
`<script nonce="${escapeHtml(nonce)}">
|
|
635
|
+
${script}
|
|
636
|
+
</script>`,
|
|
637
|
+
"</head>",
|
|
638
|
+
"<body></body>",
|
|
639
|
+
"</html>"
|
|
640
|
+
].join("\n");
|
|
641
|
+
}
|
|
642
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
643
|
+
0 && (module.exports = {
|
|
644
|
+
BRIDGE_ERROR_CODES,
|
|
645
|
+
BRIDGE_NAMESPACE,
|
|
646
|
+
BRIDGE_SURFACES,
|
|
647
|
+
BRIDGE_VERSION,
|
|
648
|
+
BridgeError,
|
|
649
|
+
HOST_EVENT_TYPES,
|
|
650
|
+
IFRAME_ATTRIBUTES,
|
|
651
|
+
INIT_RESPONSE,
|
|
652
|
+
MAX_ELEMENT_NAME_LENGTH,
|
|
653
|
+
MAX_ENVELOPE_ID_LENGTH,
|
|
654
|
+
MAX_ENVELOPE_TYPE_LENGTH,
|
|
655
|
+
MAX_MEDIA_ORIGINS,
|
|
656
|
+
READY_EVENT,
|
|
657
|
+
REQUEST_TIMEOUT_MS,
|
|
658
|
+
REQUEST_TYPES,
|
|
659
|
+
buildFrameCsp,
|
|
660
|
+
createEnvelope,
|
|
661
|
+
createNonce,
|
|
662
|
+
createSession,
|
|
663
|
+
createSrcdoc,
|
|
664
|
+
hasSession,
|
|
665
|
+
isBridgeEnvelope,
|
|
666
|
+
isBridgeErrorCode,
|
|
667
|
+
isBridgeErrorLike,
|
|
668
|
+
isBridgeSurface,
|
|
669
|
+
isHostEventType,
|
|
670
|
+
isRequestType,
|
|
671
|
+
isValidNonce,
|
|
672
|
+
isValidSession
|
|
673
|
+
});
|