Emberveil source

mobile/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;}