Emberveil sourcemobile/public/network.js
export function createSocketBridge(baseUrl,{Socket=globalThis.WebSocket}={}) {
const connections=new Map();let nextId=1,paused=false;
const delay=ms=>new Promise(resolve=>setTimeout(resolve,ms));
function close(state){state.closed=true;state.ws.close();state.wake?.();}
const natives={
async Java_BrowserSocket_connectBrowser(lib,port) {
if(paused||(port!==43594&&port!==43595))return -1;
const ws=new Socket(baseUrl+(port===43594?'/game':'/cache'));ws.binaryType='arraybuffer';
const state={ws,chunks:[],length:0,closed:false,wake:null};const id=nextId++;
connections.set(id,state);
ws.addEventListener('message',event=>{
if(!(event.data instanceof ArrayBuffer)){close(state);return;}
const bytes=new Uint8Array(event.data);if(!bytes.length)return;
state.chunks.push(bytes);state.length+=bytes.length;
if(state.length>2097152){state.chunks=[];state.length=0;close(state);return;}
state.wake?.();
});
ws.addEventListener('close',()=>{state.closed=true;state.wake?.();});
ws.addEventListener('error',()=>{state.closed=true;state.wake?.();});
const ready=await new Promise(resolve=>{
const timer=setTimeout(()=>{close(state);resolve(false);},10000);
const finish=value=>{clearTimeout(timer);resolve(value);};
ws.addEventListener('open',()=>finish(!paused&&!state.closed),{once:true});
ws.addEventListener('error',()=>finish(false),{once:true});
ws.addEventListener('close',()=>finish(false),{once:true});
});
if(!ready){connections.delete(id);return -1;}return id;
},
async Java_BrowserSocket_availableBrowser(lib,id){const s=connections.get(id);return !s?-1:s.length|| (s.closed?-1:0);},
async Java_BrowserSocket_readBrowser(lib,id,maximum,timeout) {
const state=connections.get(id);if(!state)return null;
if(!state.length&&!state.closed)await new Promise(resolve=>{
const timer=setTimeout(()=>{close(state);resolve();},timeout||30000);
state.wake=()=>{clearTimeout(timer);state.wake=null;resolve();};
});
if(!state.length)return null;
const result=new Int8Array(Math.min(maximum,state.length));let offset=0;
while(offset<result.length){const chunk=state.chunks[0],size=Math.min(chunk.length,result.length-offset);result.set(chunk.subarray(0,size),offset);offset+=size;state.length-=size;if(size===chunk.length)state.chunks.shift();else state.chunks[0]=chunk.subarray(size);}
return result;
},
async Java_BrowserSocket_writeBrowser(lib,id,data,offset,length) {
const state=connections.get(id);if(!state||state.closed)return false;
const deadline=Date.now()+30000;
while(state.ws.bufferedAmount>524288){if(state.closed||Date.now()>deadline)return false;await delay(10);}
try{if(state.ws.readyState!==1)return false;state.ws.send(new Uint8Array(data.buffer,data.byteOffset+offset,length).slice());return true;}
catch{close(state);return false;}
},
async Java_BrowserSocket_closeBrowser(lib,id){const state=connections.get(id);if(state){close(state);connections.delete(id);}},
};
return {natives,closeAll(){paused=true;for(const s of connections.values())close(s);connections.clear();},resume(){paused=false;}};
}
export function socketNatives(baseUrl){return createSocketBridge(baseUrl).natives;}