Skip to content

Commit 071ef20

Browse files
committed
[chrome] setup ipc for scramjet interface
1 parent bb3c5e2 commit 071ef20

File tree

1 file changed

+141
-11
lines changed

1 file changed

+141
-11
lines changed

packages/chrome/src/IsolatedFrame.tsx

Lines changed: 141 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
type URLMeta,
1111
setInterface,
1212
type ScramjetInterface,
13+
type Serverbound,
1314
} from "@mercuryworkshop/scramjet/bundled";
1415

1516
import scramjetWASM from "../../scramjet/dist/scramjet.wasm.wasm?url";
@@ -92,6 +93,75 @@ const cfg = {
9293

9394
setConfig(cfg);
9495

96+
// you can get to any frame by window.frames[index][index]...
97+
// so we can encode a certain frame as a sequence of indices to reach it, and then it will be available from any other frame, even cross-origin
98+
type FrameSequence = number[];
99+
100+
function findSelfSequence(
101+
target: Window,
102+
path: FrameSequence = []
103+
): FrameSequence | null {
104+
if (target == self) {
105+
return path;
106+
} else {
107+
for (let i = 0; i < target.frames.length; i++) {
108+
const child = target.frames[i];
109+
const res = findSelfSequence(child, [...path, i]);
110+
if (res) return res;
111+
}
112+
return null;
113+
}
114+
}
115+
116+
type ServerboundMethods = {
117+
[K in keyof Serverbound]: (
118+
tab: Tab,
119+
arg: Serverbound[K][0]
120+
) => Promise<Serverbound[K][1]>;
121+
};
122+
const scramjetipcserverbound: ServerboundMethods = {
123+
setCookie: async (tab, { cookie, url }) => {
124+
console.log("setCookie", cookie, url, tab);
125+
return undefined;
126+
},
127+
blobData: async (tab, { id, data }) => {
128+
return undefined;
129+
},
130+
};
131+
132+
addEventListener("message", (e) => {
133+
if (!e.data || !("$scramjetipc$type" in e.data)) return;
134+
const type = e.data.$scramjetipc$type;
135+
if (type === "request") {
136+
const method = e.data.$scramjetipc$method;
137+
const message = e.data.$scramjetipc$message;
138+
const token = e.data.$scramjetipc$token;
139+
140+
const findTab = (win: Window): Tab | null => {
141+
const f = browser.tabs.find((t) => t.frame.frame.contentWindow === win);
142+
if (f) return f;
143+
const p = findTab(win.parent);
144+
if (p) return p;
145+
// no need to worry about subframes because it can't be a tab if it's not the top frame
146+
return null;
147+
};
148+
149+
const tab = findTab(e.source as Window)!;
150+
const fn = (scramjetipcserverbound as any)[method];
151+
if (fn) {
152+
fn(tab, message).then((response: any) => {
153+
e.source!.postMessage({
154+
$scramjetipc$type: "response",
155+
$scramjetipc$token: token,
156+
$scramjetipc$message: response,
157+
});
158+
});
159+
} else {
160+
console.error("Unknown scramjet ipc method", method);
161+
}
162+
}
163+
});
164+
95165
const getInjectScripts: ScramjetInterface["getInjectScripts"] = (
96166
meta,
97167
handler,
@@ -100,17 +170,77 @@ const getInjectScripts: ScramjetInterface["getInjectScripts"] = (
100170
script
101171
) => {
102172
const injected = `
103-
$scramjetLoadClient().loadAndHook({
104-
interface: {
105-
getInjectScripts: ${getInjectScripts.toString()},
106-
onClientbound: function() { return undefined; },
107-
sendServerbound: async function() {}
108-
},
109-
config: ${JSON.stringify(config)},
110-
cookies: ${JSON.stringify(cookieJar.dump())},
111-
transport: null,
112-
});
113-
document.currentScript.remove();
173+
{
174+
const top = self.top;
175+
const sequence = ${JSON.stringify(findSelfSequence(self)!)};
176+
const target = sequence.reduce((win, idx) => win.frames[idx], top);
177+
let counter = 0;
178+
179+
let syncPool = new Map();
180+
181+
const scramjetipcclientboundmethods = {
182+
setCookie: async ({ cookie, url }) => {
183+
console.log("[clientbound] setCookie", cookie, url);
184+
return undefined;
185+
}
186+
};
187+
188+
addEventListener("message", (e) => {
189+
if (!e.data || !("$scramjetipc$type" in e.data)) return;
190+
const type = e.data.$scramjetipc$type;
191+
if (type === "response") {
192+
const token = e.data.$scramjetipc$token;
193+
const message = e.data.$scramjetipc$message;
194+
195+
const cb = syncPool.get(token);
196+
if (cb) {
197+
cb(message);
198+
syncPool.delete(token);
199+
}
200+
} else if (type === "request") {
201+
const method = e.data.$scramjetipc$method;
202+
const message = e.data.$scramjetipc$message;
203+
const token = e.data.$scramjetipc$token;
204+
205+
const fn = scramjetipcclientboundmethods[method];
206+
if (fn) {
207+
fn(message).then((response) => {
208+
e.source.postMessage({
209+
$scramjetipc$type: "response",
210+
$scramjetipc$token: token,
211+
$scramjetipc$message: response,
212+
});
213+
});
214+
} else {
215+
console.error("Unknown scramjet ipc clientbound method", method);
216+
}
217+
}
218+
});
219+
220+
const client = $scramjetLoadClient().loadAndHook({
221+
interface: {
222+
getInjectScripts: ${getInjectScripts.toString()},
223+
onClientbound: function() { return undefined; },
224+
sendServerbound: async function(type, msg) {
225+
const token = counter++;
226+
target.postMessage({
227+
$scramjetipc$type: "request",
228+
$scramjetipc$method: type,
229+
$scramjetipc$token: token,
230+
$scramjetipc$message: msg,
231+
}, "*");
232+
233+
return new Promise((res) => {
234+
syncPool.set(token, res);
235+
});
236+
}
237+
},
238+
config: ${JSON.stringify(config)},
239+
cookies: ${JSON.stringify(cookieJar.dump())},
240+
transport: null,
241+
});
242+
document.currentScript.remove();
243+
}
114244
`;
115245

116246
// for compatibility purpose

0 commit comments

Comments
 (0)