From c7601ff83e1e0ce9762fb5e8e27805fc37878eef Mon Sep 17 00:00:00 2001 From: Aviral Shukla Date: Sun, 1 Nov 2026 18:23:02 +0530 Subject: [PATCH 1/2] chore: remove dead code and trim test boilerplate --- README.md | 6 +- dist/gui.cjs | 30 +++---- package.json | 1 - src/main.test.ts | 12 +-- src/main.ts | 5 +- src/native/android.ts | 2 +- src/native/discovery.ts | 52 +------------ src/native/helpers.ts | 7 +- src/native/hub.ts | 25 +----- src/native/native.test.ts | 68 +++++----------- src/native/process.ts | 4 +- src/server.test.ts | 160 +++++++++----------------------------- src/stream-proxy.test.ts | 47 +++-------- src/stream-proxy.ts | 66 ++++------------ src/viewer.ts | 12 +-- 15 files changed, 121 insertions(+), 376 deletions(-) diff --git a/README.md b/README.md index 532b781..a429c90 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ macOS. - Android SDK Platform-Tools and Emulator, a configured Android Virtual Device, and hardware virtualization enabled. - ffmpeg with libx264 on the desktop process's PATH. -Set `ANDROID_HOME` or `ANDROID_SDK_ROOT` if your SDK is outside the standard Android Studio location. +Set `ANDROID_HOME` or `ANDROID_SDK_ROOT` if your SDK is outside the standard Android Studio location. Set `OPENCODE_DEVICE_NODE` to your Node executable if the desktop cannot find it. ## Install @@ -37,7 +37,7 @@ The panel opens the first configured emulator and starts it if needed. Use the picker to switch devices and refresh to rescan. Agent access starts enabled. Use the switch to revoke access for the current -session. Agents can only use a device you have opened. +session. Agents can only use a device you have opened. Installing an APK or shutting down an emulator requires a separate permission prompt. Tools use `device_*` names, or `device.*` in Code Mode. They can inspect the @@ -59,5 +59,5 @@ bun run bundle bun run verify:package ``` -MIT license. +MIT license. Dependency licenses are listed in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md). diff --git a/dist/gui.cjs b/dist/gui.cjs index 1cb43c0..b95884d 100644 --- a/dist/gui.cjs +++ b/dist/gui.cjs @@ -1,23 +1,23 @@ -"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const H=require("solid-js/web"),le=require("solid-js"),ft=require("@opencode/plugin/gui"),Ps=require("@opencode/schema/rpc"),u=require("effect"),Bs=8*1024*1024,$e=u.Schema.optional,be=u.Schema.String.check(u.Schema.isMaxLength(2048)),Lr=u.Schema.String.check(u.Schema.isMaxLength(1e5)),mt=u.Schema.Int.check(u.Schema.isGreaterThanOrEqualTo(0)),Dr=u.Schema.Int.check(u.Schema.isBetween({minimum:0,maximum:1e6})),ze=u.Schema.Literals(["ios","android"]).annotate({identifier:"Device.Platform"}),Ns=u.Schema.Literals(["shutdown","booting","booted","unavailable"]).annotate({identifier:"Device.DeviceState"}),As=u.Schema.Literals(["available","owned-by-this-host","owned-by-other","unknown"]).annotate({identifier:"Device.Ownership"}),Tr=u.Schema.Literals(["apps","launch","install","snapshot","press","fill","swipe","key","screenshot","stream"]).annotate({identifier:"Device.Capability"}),Ms=u.Schema.Literals(["home","back","recents","lock","volumeUp","volumeDown"]).annotate({identifier:"Device.NavigationKey"}),fn=mt.annotate({identifier:"Device.Generation"}),mn=u.Schema.String.check(u.Schema.isPattern(/^dh_[a-f0-9-]{36}$/u)).pipe(u.Schema.brand("Device.HandleToken")).annotate({identifier:"Device.HandleToken"}),Le=u.Schema.Struct({token:mn,platform:ze,deviceId:be,generation:fn}).annotate({identifier:"Device.DeviceHandle"}),Ar=u.Schema.String.check(u.Schema.isPattern(/^@?e[1-9][0-9]*$/u)).pipe(u.Schema.brand("Device.ElementRef")).annotate({identifier:"Device.ElementRef"}),Cr=u.Schema.Struct({token:mn,generation:fn,revision:mt,ref:Ar}).annotate({identifier:"Device.SnapshotReference"}),Us=u.Schema.Struct({ref:$e(Ar),role:be,name:$e(Lr),value:$e(Lr),enabled:$e(u.Schema.Boolean),focused:$e(u.Schema.Boolean)}).annotate({identifier:"Device.AccessibilityNode"}),Rs=u.Schema.Struct({token:mn,generation:fn,revision:mt,interactiveOnly:u.Schema.Boolean,complete:u.Schema.Boolean,nodes:u.Schema.Array(Us)}).annotate({identifier:"Device.Snapshot"}),hn=u.Schema.Struct({x:Dr,y:Dr}).annotate({identifier:"Device.LogicalPoint"}),js=u.Schema.Struct({id:be,name:be,platform:ze,osVersion:$e(be),state:Ns,ownership:As}).annotate({identifier:"Device.DeviceInfo"}),$s=u.Schema.Struct({supported:u.Schema.Array(Tr),unsupported:u.Schema.Array(u.Schema.Struct({capability:Tr,reason:be}))}).annotate({identifier:"Device.Capabilities"}),Ws=u.Schema.Struct({id:be,name:$e(be)}).annotate({identifier:"Device.AppInfo"}),Fs=u.Schema.Struct({app:be,state:u.Schema.Literals(["launched","foreground","unknown"])}).annotate({identifier:"Device.LaunchResult"}),zs=u.Schema.Struct({installed:u.Schema.Boolean,app:$e(be)}).annotate({identifier:"Device.InstallResult"}),Vs=u.Schema.Struct({data:u.Schema.Uint8ArrayFromBase64.check(u.Schema.isMaxLength(Bs)),width:mt,height:mt,deviceId:be,platform:ze}).annotate({identifier:"Device.Screenshot"}),Gs=u.Schema.Literals(["mjpeg","h264"]).annotate({identifier:"Device.StreamProtocol"}),qs=u.Schema.Struct({platform:ze,deviceId:be,baseUrl:be,token:be,protocol:Gs,loopback:u.Schema.Literal(!0)}).annotate({identifier:"Device.StreamDescriptor"}),Hs=u.Schema.Literals(["missing-prerequisite","permission-denied","device-busy","detached","stale-reference","unsupported","not-found","invalid-artifact","timeout","helper-failed","internal"]).annotate({identifier:"Device.DeviceErrorCode"}),Sn=u.Schema.Struct({code:Hs,message:be,mayHaveExecuted:u.Schema.Boolean}).annotate({identifier:"Device.DeviceError"}),Ze=u.Schema.optional,We=u.Schema.String.check(u.Schema.isMaxLength(2048)),Mr=We.check(u.Schema.isMinLength(1)),pn={sessionID:Mr,connectionID:u.Schema.String},Ir={...pn,requestID:u.Schema.String},Or=u.Schema.Number.check(u.Schema.isInt(),u.Schema.isBetween({minimum:1,maximum:1e6})),Pr=u.Schema.optional(u.Schema.Struct({width:Or,height:Or})),et=u.Schema.Struct({sessionID:Mr,serverID:We,location:u.Schema.optionalKey(u.Schema.Struct({directory:We}))}),Bt=u.Schema.Struct({handle:Le,capabilities:$s,agentAccess:u.Schema.Boolean}).annotate({identifier:"DeviceBridge.Attachment"}),Ur=u.Schema.Union([u.Schema.Struct({type:u.Schema.Literal("list"),platform:Ze(ze)}),u.Schema.Struct({type:u.Schema.Literal("state")}),u.Schema.Struct({type:u.Schema.Literal("open"),platform:ze,deviceId:We,boot:Ze(u.Schema.Boolean)}),u.Schema.Struct({type:u.Schema.Literal("apps"),handle:Le}),u.Schema.Struct({type:u.Schema.Literal("launch"),handle:Le,app:We,deepLink:Ze(We),relaunch:Ze(u.Schema.Boolean)}),u.Schema.Struct({type:u.Schema.Literal("install"),handle:Le,artifactPath:We}),u.Schema.Struct({type:u.Schema.Literal("snapshot"),handle:Le,interactiveOnly:Ze(u.Schema.Boolean)}),u.Schema.Struct({type:u.Schema.Literal("press"),handle:Le,reference:Cr}),u.Schema.Struct({type:u.Schema.Literal("tap"),handle:Le,point:hn,viewport:Pr}),u.Schema.Struct({type:u.Schema.Literal("fill"),handle:Le,reference:Cr,text:We}),u.Schema.Struct({type:u.Schema.Literal("swipe"),handle:Le,from:hn,to:hn,viewport:Pr}),u.Schema.Struct({type:u.Schema.Literal("key"),handle:Le,key:Ms}),u.Schema.Struct({type:u.Schema.Literal("screenshot"),handle:Le}),u.Schema.Struct({type:u.Schema.Literal("close"),handle:Le,shutdown:Ze(u.Schema.Boolean)})]).pipe(u.Schema.toTaggedUnion("type")).annotate({identifier:"DeviceBridge.Operation"}),Js=u.Schema.Union([u.Schema.Struct({type:u.Schema.Literal("devices"),devices:u.Schema.Array(js)}),u.Schema.Struct({type:u.Schema.Literal("state"),state:u.Schema.NullOr(Bt)}),u.Schema.Struct({type:u.Schema.Literal("attachment"),attachment:Bt}),u.Schema.Struct({type:u.Schema.Literal("apps"),apps:u.Schema.Array(Ws)}),u.Schema.Struct({type:u.Schema.Literal("launch"),result:Fs}),u.Schema.Struct({type:u.Schema.Literal("install"),result:zs}),u.Schema.Struct({type:u.Schema.Literal("snapshot"),snapshot:Rs}),u.Schema.Struct({type:u.Schema.Literal("screenshot"),screenshot:Vs}),u.Schema.Struct({type:u.Schema.Literal("done")})]).pipe(u.Schema.toTaggedUnion("type")).annotate({identifier:"DeviceBridge.Value"}),Pt=u.Schema.Union([u.Schema.Struct({type:u.Schema.Literal("success"),value:Js}),u.Schema.Struct({type:u.Schema.Literal("failure"),error:Sn})]).pipe(u.Schema.toTaggedUnion("type")).annotate({identifier:"DeviceBridge.Outcome"}),Xs=u.Schema.Union([u.Schema.Struct({type:u.Schema.Literal("success"),stream:qs}),u.Schema.Struct({type:u.Schema.Literal("failure"),error:Sn})]).pipe(u.Schema.toTaggedUnion("type")).annotate({identifier:"DeviceBridge.StreamOutcome"}),Ys=u.Schema.Union([u.Schema.Struct({type:u.Schema.Literal("attached"),connectionID:u.Schema.String,version:u.Schema.Literal(1)}),u.Schema.Struct({type:u.Schema.Literal("command"),connectionID:u.Schema.String,requestID:u.Schema.String}),u.Schema.Struct({type:u.Schema.Literal("cancel"),connectionID:u.Schema.String,requestID:u.Schema.String})]).pipe(u.Schema.toTaggedUnion("type")).annotate({identifier:"DeviceBridge.Control"}),Oe=u.Schema.toStandardSchemaV1,It={unavailable:Oe(Sn)};Ps.Rpc.define({id:"device.control",methods:{attach:{input:Oe(u.Schema.Struct({...pn,version:u.Schema.Literal(1)})),output:Oe(u.Schema.Literals(["closed","replaced"])),errors:It},state:{input:Oe(u.Schema.Struct({...pn,state:u.Schema.NullOr(Bt)})),output:Oe(u.Schema.Void),errors:It},command:{input:Oe(u.Schema.Struct(Ir)),output:Oe(Ur),errors:It},result:{input:Oe(u.Schema.Struct({...Ir,outcome:Pt})),output:Oe(u.Schema.Void),errors:It}},events:{control:{schema:Oe(Ys)}}});const Qs=u.Schema.String.check(u.Schema.isMaxLength(2048)),Ks=ft.Ipc.define({id:"demul.device.bridge.v2",state:u.Schema.Array(u.Schema.Struct({scope:et,state:u.Schema.NullOr(Bt)})),methods:{connect:{input:et},disconnect:{input:et},list:{input:u.Schema.Struct({platform:u.Schema.optional(ze),serverID:Qs}),output:Pt},access:{input:u.Schema.Struct({scope:et,enabled:u.Schema.Boolean}),output:Pt},manual:{input:u.Schema.Struct({scope:et,operation:Ur}),output:Pt},stream:{input:u.Schema.Struct({scope:et,handle:Le}),output:Xs}}});function Zs(g,T,E,N){if(!(!g||g!==T||E!=="playing"||!Number.isSafeInteger(N.width)||!Number.isSafeInteger(N.height)||N.width<=0||N.height<=0))return{token:g,width:N.width,height:N.height}}function eo(g,T){return!!T&&g.token===T.token&&g.width===T.width&&g.height===T.height}const to=1397050709,Rr=16*1024*1024;function no(g){let T=!1,E;const N=()=>{const n=T&&document.visibilityState==="visible";if(n&&!E){E=g.stream.protocol==="h264"?co(g):lo(g);return}n||!E||(E(),E=void 0,g.onState(T?"paused":"idle"))},V=()=>N();return document.addEventListener("visibilitychange",V),{active(n){T=n,N()},destroy(){T=!1,document.removeEventListener("visibilitychange",V),E?.(),E=void 0,g.canvas.width=0,g.canvas.height=0,g.onState("idle")}}}function ro(g){const T=new Uint8Array(g),E=new DataView(g);if(T.byteLength<4)return;if(E.getUint32(0)!==to)return Br(T)?{bytes:T,key:Ot(T,5)||Ot(T,7),timestamp:performance.now()*1e3}:void 0;if(T.byteLength<16)return;const N=E.getUint8(4),V=N===1?16:N===2?24:0;if(!V||T.byteLength<=V)return;const n=E.getBigUint64(8);if(n>BigInt(Number.MAX_SAFE_INTEGER))return;const ie=T.subarray(V);if(Br(ie))return{bytes:ie,key:(E.getUint8(5)&1)===1||Ot(ie,5)||Ot(ie,7),timestamp:Number(n),serverTimestamp:N===2?Number(E.getBigUint64(16))/1e3:void 0}}function io(g){const T=$r(g).find(E=>(E[0]&31)===7);if(!(!T||T.byteLength<4))return`avc1.${[T[1],T[2],T[3]].map(E=>E.toString(16).padStart(2,"0")).join("")}`}function so(g,T,E){if(![g.left,g.top,g.width,g.height,T.width,T.height,E.x,E.y].every(Number.isFinite)||g.width<=0||g.height<=0||!Number.isSafeInteger(T.width)||!Number.isSafeInteger(T.height)||T.width<=0||T.height<=0)return;const N=Math.min(g.width/T.width,g.height/T.height),V=T.width*N,n=T.height*N,ie=E.x-g.left-(g.width-V)/2,F=E.y-g.top-(g.height-n)/2;if(!(ie<0||F<0||ie>=V||F>=n))return{x:Math.min(T.width-1,Math.floor(ie/N)),y:Math.min(T.height-1,Math.floor(F/N))}}function oo(g){if(!g||/[\r\n]/.test(g))return;const T=g.split(";");if(T.shift()?.trim().toLowerCase()!=="multipart/x-mixed-replace")return;const E=T.map(n=>n.trim()).find(n=>n.toLowerCase().startsWith("boundary="))?.slice(9).trim();if(!E)return;const N=E.startsWith('"')&&E.endsWith('"')?E.slice(1,-1):E,V=N.startsWith("--")?N.slice(2):N;if(!(!V||/[\r\n]/.test(V)))return V}function ao(g,T){const E=new TextEncoder().encode(`--${T}`),N=new Uint8Array([13,10,13,10]),V=[];let n=0,ie=0;for(;ng.byteLength)return{frames:V,rest:g.slice(F)};if(g[q]!==13||g[q+1]!==10){n=q;continue}const G=Nr(g,N,q+2);if(G<0)return{frames:V,rest:g.slice(F)};const se=new TextDecoder("ascii").decode(g.subarray(q+2,G)),R=se.match(/(?:^|\r\n)Content-Length:\s*(\d+)(?:\r\n|$)/i)?.[1],ce=se.match(/(?:^|\r\n)Content-Type:\s*([^\r\n]+)(?:\r\n|$)/i)?.[1].trim().toLowerCase(),re=R?Number(R):0,de=G+N.byteLength,he=de+re;if(!Number.isSafeInteger(re)||re<=0||re>Rr||ce!=="image/jpeg"){n=G+N.byteLength;continue}if(he>g.byteLength)return{frames:V,rest:g.slice(F)};const pe=g.slice(de,he);pe.byteLength>=4&&pe[0]===255&&pe[1]===216&&pe.at(-2)===255&&pe.at(-1)===217&&V.push(pe),ie=he,n=he}return{frames:V,rest:g.slice(ie)}}function co(g){if(typeof VideoDecoder!="function"||typeof EncodedVideoChunk!="function")return g.onState("paused"),g.onError("H.264 device video requires WebCodecs support in this browser"),()=>{};const T=new URL("/video?frame-meta=1",new URL(g.stream.baseUrl).origin);T.protocol=T.protocol==="https:"?"wss:":"ws:";const E=new WebSocket(T,`opencode-device.${g.stream.token}`);let N,V,n=0,ie=!0,F=!1,q=!1;E.binaryType="arraybuffer",g.onState("connecting");const G=()=>{if(E.readyState===WebSocket.OPEN)try{E.send(JSON.stringify({type:"reset-video",ack:!1}))}catch{return}},se=(re=!1)=>{n++;const de=N,he=q;if(N=void 0,V=void 0,ie=!0,q=!1,de&&de.state!=="closed")try{de.reset()}finally{de.close()}re&&he&&!F&&g.onState("connecting"),re&&G()},R=re=>{F||(g.onError(re),g.onState("paused"))},ce=(re,de)=>{if(!(F||N!==de||de.state!=="configured"))try{de.decode(new EncodedVideoChunk({type:re.key?"key":"delta",timestamp:re.timestamp,data:re.bytes})),ie=!1}catch(he){se(!0),R(`H.264 decode failed: ${he instanceof Error?he.message:String(he)}`)}};return E.addEventListener("open",()=>G()),E.addEventListener("message",re=>{if(F)return;if(typeof re.data=="string"){ho(re.data)&&se(!0);return}if(!(re.data instanceof ArrayBuffer))return;const de=ro(re.data);if(!de)return;if(N&&N.decodeQueueSize>2){se(!0);return}if(ie&&!de.key)return;const he=io(de.bytes)??V;if(N&&he===V){ce(de,N);return}if(!he){G();return}se(),V=he;const pe=n,Ne={codec:he,optimizeForLatency:!0,hardwareAcceleration:"prefer-hardware"};VideoDecoder.isConfigSupported(Ne).then(xe=>{if(F||pe!==n)return;if(!xe.supported){R(`H.264 codec ${he} is not supported by this browser`);return}let _e;_e=new VideoDecoder({output(Se){if(F||N!==_e){Se.close();return}try{jr(g,Se,Se.displayWidth,Se.displayHeight)&&!q&&(q=!0,g.onState("playing"))}catch(Pe){se(!0),R(`Device video draw failed: ${Pe instanceof Error?Pe.message:String(Pe)}`)}finally{Se.close()}},error(Se){F||N!==_e||(se(!0),R(`H.264 decoder failed: ${Se.message}`))}});try{_e.configure(xe.config??Ne)}catch(Se){_e.close(),R(`H.264 decoder configuration failed: ${Se instanceof Error?Se.message:String(Se)}`);return}if(F||pe!==n){_e.close();return}N=_e,ce(de,_e)},xe=>R(`H.264 decoder support check failed: ${xe instanceof Error?xe.message:String(xe)}`))}),E.addEventListener("error",()=>R("Device video connection failed")),E.addEventListener("close",()=>{se(),F||g.onState("paused")}),()=>{F=!0,E.close(),se()}}function lo(g){const T=new AbortController;let E,N,V=!1,n=!1;return g.onState("connecting"),fetch(new URL("/video",new URL(g.stream.baseUrl).origin),{method:"GET",headers:{Authorization:`Bearer ${g.stream.token}`},signal:T.signal}).then(async ie=>{if(!ie.ok||!ie.body)throw new Error(`Device video returned ${ie.status}`);const F=oo(ie.headers.get("content-type"));if(!F)throw new Error("Device video returned an invalid MJPEG content type");E=ie.body.getReader();let q=new Uint8Array;for(;!V;){const G=await E.read();if(G.done)break;q=uo(q,G.value);const se=ao(q,F);q=se.rest;for(const R of se.frames){const ce=await createImageBitmap(new Blob([new Uint8Array(R).buffer],{type:"image/jpeg"}));N=ce;try{if(V)continue;jr(g,ce,ce.width,ce.height)&&!n&&(n=!0,g.onState("playing"))}finally{ce.close(),N===ce&&(N=void 0)}}if(q.byteLength>Rr)throw new Error("Device MJPEG stream exceeded 16 MiB without a complete frame")}V||g.onState("paused")}).catch(ie=>{V||(g.onState("paused"),g.onError(ie instanceof Error?ie.message:String(ie)))}).finally(()=>{E?.releaseLock(),E=void 0}),()=>{V=!0,T.abort(),E?.cancel().catch(()=>{}),N?.close(),N=void 0}}function jr(g,T,E,N){if(!Number.isSafeInteger(E)||!Number.isSafeInteger(N)||E<=0||N<=0)return!1;(g.canvas.width!==E||g.canvas.height!==N)&&(g.canvas.width=E,g.canvas.height=N);const V=g.canvas.getContext("2d",{alpha:!1});return V?(V.drawImage(T,0,0,E,N),!0):!1}function ho(g){try{const T=JSON.parse(g);return!T||typeof T!="object"||!("type"in T)||T.type!=="video-session"||!("size"in T)||!T.size||typeof T.size!="object"||!("width"in T.size)||!("height"in T.size)||typeof T.size.width!="number"||typeof T.size.height!="number"||!Number.isSafeInteger(T.size.width)||!Number.isSafeInteger(T.size.height)||T.size.width<=0||T.size.height<=0?void 0:{width:T.size.width,height:T.size.height}}catch{return}}function $r(g){const T=[];for(let E=0;Eg.subarray(E.nal,T[N+1]?.code??g.byteLength))}function Br(g){return g.byteLength>=4&&g[0]===0&&g[1]===0&&g[2]===1?!0:g.byteLength>=5&&g[0]===0&&g[1]===0&&g[2]===0&&g[3]===1}function Ot(g,T){return $r(g).some(E=>(E[0]&31)===T)}function uo(g,T){const E=new Uint8Array(g.byteLength+T.byteLength);return E.set(g),E.set(T,g.byteLength),E}function Nr(g,T,E){for(let N=E;N<=g.byteLength-T.byteLength;N++){let V=!0;for(let n=0;n"),mo=H.template("