@@ -10,6 +10,7 @@ import {
1010 type URLMeta ,
1111 setInterface ,
1212 type ScramjetInterface ,
13+ type Serverbound ,
1314} from "@mercuryworkshop/scramjet/bundled" ;
1415
1516import scramjetWASM from "../../scramjet/dist/scramjet.wasm.wasm?url" ;
@@ -92,6 +93,75 @@ const cfg = {
9293
9394setConfig ( 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+
95165const 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