Files
overlord/Overlord-Server/public/assets/plugin-bridge.js
T

168 lines
4.6 KiB
JavaScript
Raw Normal View History

2026-03-05 00:04:26 -06:00
(function () {
const params = new URLSearchParams(location.search);
const token = params.get("token") || "";
const origin = params.get("origin") || "";
let seq = 0;
const pending = new Map();
function base64ToArrayBuffer(base64) {
const binary = atob(base64 || "");
const len = binary.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i += 1) {
bytes[i] = binary.charCodeAt(i);
}
return bytes.buffer;
}
function makeResponse(payload) {
const headers = new Map(Object.entries(payload.headers || {}));
const bodyText = payload.bodyText || "";
const bodyBase64 = payload.bodyBase64 || "";
const response = {
ok: Boolean(payload.ok),
status: payload.status || 0,
statusText: payload.statusText || "",
headers,
text: async () => bodyText,
json: async () => JSON.parse(bodyText || "{}"),
arrayBuffer: async () => base64ToArrayBuffer(bodyBase64),
blob: async () => new Blob([base64ToArrayBuffer(bodyBase64)], {
type: payload.contentType || "application/octet-stream",
}),
};
return response;
}
async function bridgeFetch(input, init = {}) {
const requestUrl = typeof input === "string" ? input : input.url;
const id = `req_${Date.now()}_${seq++}`;
const request = {
url: requestUrl,
method: init.method || "GET",
headers: init.headers || {},
body: init.body || undefined,
};
const responsePromise = new Promise((resolve, reject) => {
pending.set(id, { resolve, reject });
window.parent.postMessage(
{ type: "plugin-fetch", id, token, request },
origin || "*",
);
setTimeout(() => {
if (!pending.has(id)) return;
pending.delete(id);
reject(new Error("plugin fetch timeout"));
}, 30_000);
});
const payload = await responsePromise;
if (payload.error) {
throw new Error(payload.message || payload.error);
}
return makeResponse(payload);
}
window.addEventListener("message", (event) => {
const data = event.data;
if (!data || data.type !== "plugin-fetch-response") return;
const waiter = pending.get(data.id);
if (!waiter) return;
pending.delete(data.id);
waiter.resolve(data);
});
window.fetch = bridgeFetch;
let wsSeq = 0;
const wsWaiters = new Map();
const wsHandlers = new Map();
function createPluginWebSocket(url) {
const id = `pws_${Date.now()}_${wsSeq++}`;
const handlers = { onopen: null, onmessage: null, onclose: null, onerror: null };
wsHandlers.set(id, handlers);
window.parent.postMessage(
{ type: "plugin-ws-open", id, token, url },
origin || "*",
);
return {
id,
set onopen(fn) {
handlers.onopen = fn;
},
set onmessage(fn) {
handlers.onmessage = fn;
},
set onclose(fn) {
handlers.onclose = fn;
},
set onerror(fn) {
handlers.onerror = fn;
},
send(data) {
if (data instanceof ArrayBuffer) {
const bytes = new Uint8Array(data);
let binary = "";
for (let i = 0; i < bytes.length; i += 1) {
binary += String.fromCharCode(bytes[i]);
}
window.parent.postMessage(
{ type: "plugin-ws-send", id, token, kind: "binary", data: btoa(binary) },
origin || "*",
);
return;
}
window.parent.postMessage(
{ type: "plugin-ws-send", id, token, kind: "text", data: String(data) },
origin || "*",
);
},
close() {
window.parent.postMessage(
{ type: "plugin-ws-close", id, token },
origin || "*",
);
wsHandlers.delete(id);
},
};
}
window.addEventListener("message", (event) => {
const data = event.data;
if (!data || !data.type) return;
if (!data.id) return;
const handlers = wsHandlers.get(data.id);
if (!handlers) return;
if (data.type === "plugin-ws-opened") {
handlers.onopen?.();
return;
}
if (data.type === "plugin-ws-closed") {
handlers.onclose?.();
wsHandlers.delete(data.id);
return;
}
if (data.type === "plugin-ws-error") {
handlers.onerror?.(new Error(data.error || "ws_error"));
return;
}
if (data.type === "plugin-ws-message") {
if (data.payload?.kind === "binary") {
const buffer = base64ToArrayBuffer(data.payload.data);
handlers.onmessage?.({ data: buffer });
} else {
handlers.onmessage?.({ data: data.payload?.data ?? "" });
}
}
});
window.createPluginWebSocket = createPluginWebSocket;
})();