168 lines
4.6 KiB
JavaScript
168 lines
4.6 KiB
JavaScript
(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;
|
||
|
|
})();
|