scramjet/src/client/shared/postmessage.ts
2024-10-17 22:14:23 -05:00

75 lines
3.1 KiB
TypeScript

import { iswindow } from "..";
import { SCRAMJETCLIENT } from "../../symbols";
import { ScramjetClient } from "../client";
import { POLLUTANT } from "../shared/realm";
export default function (client: ScramjetClient) {
if (iswindow)
client.Proxy("window.postMessage", {
apply(ctx) {
// so we need to send the real origin here, since the recieving window can't possibly know.
// except, remember that this code is being ran in a different realm than the invoker, so if we ask our `client` it may give us the wrong origin
// if we were given any object that came from the real realm we can use that to get the real origin
// and this works in every case EXCEPT for the fact that all three arguments can be strings which are copied instead of cloned
// so we have to use `$setrealm` which will pollute this with an object from the real realm
let pollutant;
if (typeof ctx.args[0] === "object" && ctx.args[0] !== null) {
pollutant = ctx.args[0]; // try to use the first object we can find because it's more reliable
} else if (typeof ctx.args[2] === "object" && ctx.args[2] !== null) {
pollutant = ctx.args[2]; // next try to use transfer
} else if (
POLLUTANT in ctx.this &&
typeof ctx.this[POLLUTANT] === "object" &&
ctx.this[POLLUTANT] !== null
) {
pollutant = ctx.this[POLLUTANT]; // lastly try to use the object from $setrealm
} else {
pollutant = {}; // give up
}
// and now we can steal Function from the caller's realm
const {
constructor: { constructor: Function },
} = pollutant;
// invoking stolen function will give us the caller's globalThis, remember scramjet has already proxied it!!!
const callerGlobalThisProxied: Self = Function("return globalThis")();
const callerClient = callerGlobalThisProxied[SCRAMJETCLIENT];
// this WOULD be enough but the source argument of MessageEvent has to return the caller's window
// and if we just call it normally it would be coming from here, which WILL NOT BE THE CALLER'S because the accessor is from the parent
// so with the stolen function we wrap postmessage so the source will truly be the caller's window (remember that function is scramjet's!!!)
const wrappedPostMessage = Function("...args", "this(...args)");
ctx.args[0] = {
$scramjet$messagetype: "window",
$scramjet$origin: callerClient.url.origin,
$scramjet$data: ctx.args[0],
};
// * origin because obviously
if (typeof ctx.args[1] === "string") ctx.args[1] = "*";
if (typeof ctx.args[1] === "object") ctx.args[1].targetOrigin = "*";
ctx.return(wrappedPostMessage.call(ctx.fn, ...ctx.args));
},
});
const toproxy = ["MessagePort.prototype.postMessage"];
if (self.Worker) toproxy.push("Worker.prototype.postMessage");
if (!iswindow) toproxy.push("self.postMessage"); // only do the generic version if we're in a worker
client.Proxy(toproxy, {
apply(ctx) {
// origin/source doesn't need to be preserved - it's null in the message event
ctx.args[0] = {
$scramjet$messagetype: "worker",
$scramjet$data: ctx.args[0],
};
},
});
}