@limrun/ui 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});require('./index.css');const w=require("react/jsx-runtime"),y=require("react");function ne(E){var a,D,i="";if(typeof E=="string"||typeof E=="number")i+=E;else if(typeof E=="object")if(Array.isArray(E)){var l=E.length;for(a=0;a<l;a++)E[a]&&(D=ne(E[a]))&&(i&&(i+=" "),i+=D)}else for(D in E)E[D]&&(i&&(i+=" "),i+=D);return i}function _e(){for(var E,a,D=0,i="",l=arguments.length;D<l;D++)(E=arguments[D])&&(a=ne(E))&&(i&&(i+=" "),i+=a);return i}const j={INJECT_KEYCODE:0,INJECT_TOUCH_EVENT:2,SET_CLIPBOARD:9},b={ACTION_DOWN:0,ACTION_UP:1,ACTION_MOVE:2,ACTION_CANCEL:3,BUTTON_PRIMARY:1},e={ACTION_DOWN:0,ACTION_UP:1,META_NONE:0,META_SHIFT_ON:1,META_ALT_ON:2,META_CTRL_ON:4096,META_META_ON:65536,KEYCODE_0:7,KEYCODE_1:8,KEYCODE_2:9,KEYCODE_3:10,KEYCODE_4:11,KEYCODE_5:12,KEYCODE_6:13,KEYCODE_7:14,KEYCODE_8:15,KEYCODE_9:16,DPAD_UP:19,DPAD_DOWN:20,DPAD_LEFT:21,DPAD_RIGHT:22,KEYCODE_A:29,KEYCODE_B:30,KEYCODE_C:31,KEYCODE_D:32,KEYCODE_E:33,KEYCODE_F:34,KEYCODE_G:35,KEYCODE_H:36,KEYCODE_I:37,KEYCODE_J:38,KEYCODE_K:39,KEYCODE_L:40,KEYCODE_M:41,KEYCODE_N:42,KEYCODE_O:43,KEYCODE_P:44,KEYCODE_Q:45,KEYCODE_R:46,KEYCODE_S:47,KEYCODE_T:48,KEYCODE_U:49,KEYCODE_V:50,KEYCODE_W:51,KEYCODE_X:52,KEYCODE_Y:53,KEYCODE_Z:54,KEYCODE_COMMA:55,KEYCODE_PERIOD:56,KEYCODE_ALT_LEFT:57,KEYCODE_ALT_RIGHT:58,KEYCODE_SHIFT_LEFT:59,KEYCODE_SHIFT_RIGHT:60,KEYCODE_TAB:61,KEYCODE_SPACE:62,ENTER:66,DEL:67,KEYCODE_GRAVE:68,KEYCODE_MINUS:69,KEYCODE_EQUALS:70,KEYCODE_LEFT_BRACKET:71,KEYCODE_RIGHT_BRACKET:72,KEYCODE_BACKSLASH:73,KEYCODE_SEMICOLON:74,KEYCODE_APOSTROPHE:75,KEYCODE_SLASH:76,MENU:82,KEYCODE_PAGE_UP:92,KEYCODE_PAGE_DOWN:93,KEYCODE_ESCAPE:111,FORWARD_DEL:112,KEYCODE_CTRL_LEFT:113,KEYCODE_CTRL_RIGHT:114,KEYCODE_META_LEFT:117,KEYCODE_META_RIGHT:118,KEYCODE_MOVE_HOME:122,KEYCODE_MOVE_END:123,KEYCODE_INSERT:124,KEYCODE_F1:131,KEYCODE_F2:132,KEYCODE_F3:133,KEYCODE_F4:134,KEYCODE_F5:135,KEYCODE_F6:136,KEYCODE_F7:137,KEYCODE_F8:138,KEYCODE_F9:139,KEYCODE_F10:140,KEYCODE_F11:141,KEYCODE_F12:142,KEYCODE_NUMPAD_0:144,KEYCODE_NUMPAD_1:145,KEYCODE_NUMPAD_2:146,KEYCODE_NUMPAD_3:147,KEYCODE_NUMPAD_4:148,KEYCODE_NUMPAD_5:149,KEYCODE_NUMPAD_6:150,KEYCODE_NUMPAD_7:151,KEYCODE_NUMPAD_8:152,KEYCODE_NUMPAD_9:153,KEYCODE_NUMPAD_DIVIDE:154,KEYCODE_NUMPAD_MULTIPLY:155,KEYCODE_NUMPAD_SUBTRACT:156,KEYCODE_NUMPAD_ADD:157,KEYCODE_NUMPAD_DOT:158,KEYCODE_NUMPAD_COMMA:159,KEYCODE_NUMPAD_ENTER:160,KEYCODE_NUMPAD_EQUALS:161},re={KeyA:e.KEYCODE_A,KeyB:e.KEYCODE_B,KeyC:e.KEYCODE_C,KeyD:e.KEYCODE_D,KeyE:e.KEYCODE_E,KeyF:e.KEYCODE_F,KeyG:e.KEYCODE_G,KeyH:e.KEYCODE_H,KeyI:e.KEYCODE_I,KeyJ:e.KEYCODE_J,KeyK:e.KEYCODE_K,KeyL:e.KEYCODE_L,KeyM:e.KEYCODE_M,KeyN:e.KEYCODE_N,KeyO:e.KEYCODE_O,KeyP:e.KEYCODE_P,KeyQ:e.KEYCODE_Q,KeyR:e.KEYCODE_R,KeyS:e.KEYCODE_S,KeyT:e.KEYCODE_T,KeyU:e.KEYCODE_U,KeyV:e.KEYCODE_V,KeyW:e.KEYCODE_W,KeyX:e.KEYCODE_X,KeyY:e.KEYCODE_Y,KeyZ:e.KEYCODE_Z,Digit0:e.KEYCODE_0,Digit1:e.KEYCODE_1,Digit2:e.KEYCODE_2,Digit3:e.KEYCODE_3,Digit4:e.KEYCODE_4,Digit5:e.KEYCODE_5,Digit6:e.KEYCODE_6,Digit7:e.KEYCODE_7,Digit8:e.KEYCODE_8,Digit9:e.KEYCODE_9,Backquote:e.KEYCODE_GRAVE,Minus:e.KEYCODE_MINUS,Equal:e.KEYCODE_EQUALS,BracketLeft:e.KEYCODE_LEFT_BRACKET,BracketRight:e.KEYCODE_RIGHT_BRACKET,Backslash:e.KEYCODE_BACKSLASH,Semicolon:e.KEYCODE_SEMICOLON,Quote:e.KEYCODE_APOSTROPHE,Comma:e.KEYCODE_COMMA,Period:e.KEYCODE_PERIOD,Slash:e.KEYCODE_SLASH,Space:e.KEYCODE_SPACE,Tab:e.KEYCODE_TAB,Escape:e.KEYCODE_ESCAPE,ArrowUp:e.DPAD_UP,ArrowDown:e.DPAD_DOWN,ArrowLeft:e.DPAD_LEFT,ArrowRight:e.DPAD_RIGHT,Enter:e.ENTER,Backspace:e.DEL,Delete:e.FORWARD_DEL,Home:e.KEYCODE_MOVE_HOME,End:e.KEYCODE_MOVE_END,PageUp:e.KEYCODE_PAGE_UP,PageDown:e.KEYCODE_PAGE_DOWN,Insert:e.KEYCODE_INSERT,F1:e.KEYCODE_F1,F2:e.KEYCODE_F2,F3:e.KEYCODE_F3,F4:e.KEYCODE_F4,F5:e.KEYCODE_F5,F6:e.KEYCODE_F6,F7:e.KEYCODE_F7,F8:e.KEYCODE_F8,F9:e.KEYCODE_F9,F10:e.KEYCODE_F10,F11:e.KEYCODE_F11,F12:e.KEYCODE_F12,ShiftLeft:e.KEYCODE_SHIFT_LEFT,ShiftRight:e.KEYCODE_SHIFT_RIGHT,ControlLeft:e.KEYCODE_CTRL_LEFT,ControlRight:e.KEYCODE_CTRL_RIGHT,AltLeft:e.KEYCODE_ALT_LEFT,AltRight:e.KEYCODE_ALT_RIGHT,MetaLeft:e.KEYCODE_META_LEFT,MetaRight:e.KEYCODE_META_RIGHT,ContextMenu:e.MENU,Numpad0:e.KEYCODE_NUMPAD_0,Numpad1:e.KEYCODE_NUMPAD_1,Numpad2:e.KEYCODE_NUMPAD_2,Numpad3:e.KEYCODE_NUMPAD_3,Numpad4:e.KEYCODE_NUMPAD_4,Numpad5:e.KEYCODE_NUMPAD_5,Numpad6:e.KEYCODE_NUMPAD_6,Numpad7:e.KEYCODE_NUMPAD_7,Numpad8:e.KEYCODE_NUMPAD_8,Numpad9:e.KEYCODE_NUMPAD_9,NumpadDivide:e.KEYCODE_NUMPAD_DIVIDE,NumpadMultiply:e.KEYCODE_NUMPAD_MULTIPLY,NumpadSubtract:e.KEYCODE_NUMPAD_SUBTRACT,NumpadAdd:e.KEYCODE_NUMPAD_ADD,NumpadDecimal:e.KEYCODE_NUMPAD_DOT,NumpadComma:e.KEYCODE_NUMPAD_COMMA,NumpadEnter:e.KEYCODE_NUMPAD_ENTER,NumpadEqual:e.KEYCODE_NUMPAD_EQUALS};function De(E,a,D,i,l,Y,o=1,c=0,s=0){const u=new ArrayBuffer(32),N=new DataView(u);let d=0;return N.setUint8(d,j.INJECT_TOUCH_EVENT),d+=1,N.setUint8(d,E),d+=1,N.setBigInt64(d,BigInt(a)),d+=8,N.setInt32(d,Math.round(l),!0),d+=4,N.setInt32(d,Math.round(Y),!0),d+=4,N.setUint16(d,D,!0),d+=2,N.setUint16(d,i,!0),d+=2,N.setInt16(d,Math.round(o*65535),!0),d+=2,N.setInt32(d,c,!0),d+=4,N.setInt32(d,s,!0),u}function Ce(E,a=!0){const i=new TextEncoder().encode(E),l=new ArrayBuffer(14+i.length),Y=new DataView(l);let o=0;return Y.setUint8(o,j.SET_CLIPBOARD),o+=1,Y.setBigInt64(o,BigInt(0),!1),o+=8,Y.setUint8(o,a?1:0),o+=1,Y.setUint32(o,i.length,!1),o+=4,new Uint8Array(l,o).set(i),l}function W(E,a,D=0,i=0){const l=new ArrayBuffer(14),Y=new DataView(l);let o=0;return Y.setUint8(o,j.INJECT_KEYCODE),o+=1,Y.setUint8(o,E),o+=1,Y.setInt32(o,a,!0),o+=4,Y.setInt32(o,D,!0),o+=4,Y.setInt32(o,i,!0),l}const T=(...E)=>{window.debugRemoteControl&&console.log(...E)},f=(...E)=>{window.debugRemoteControl&&console.warn(...E)};function Oe(E){const a=E.code,D=re[a];if(!D)return f(`Unknown event.code: ${a}, key: ${E.key}`),null;let i=e.META_NONE;const l=a>="KeyA"&&a<="KeyZ",Y=E.getModifierState("CapsLock"),o=E.shiftKey;let c=o;return l&&(c=o!==Y),c&&(i|=e.META_SHIFT_ON),E.ctrlKey&&(i|=e.META_CTRL_ON),E.altKey&&(i|=e.META_ALT_ON),E.metaKey&&(i|=e.META_META_ON),{keycode:D,metaState:i}}const Ke=y.forwardRef(({className:E,url:a,token:D,sessionId:i,openUrl:l},Y)=>{const o=y.useRef(null),c=y.useRef(null),s=y.useRef(null),u=y.useRef(null),[N,d]=y.useState(!1),I=y.useRef(void 0),S=y.useRef(new Map),P=y.useRef(new Map),p=y.useRef(new Map),m=y.useMemo(()=>i||Math.random().toString(36).substring(2,15)+Math.random().toString(36).substring(2,15),[i]),C=r=>{T(r)},U=r=>{!u.current||u.current.readyState!=="open"||u.current.send(r)},M=r=>{if(r.preventDefault(),r.stopPropagation(),!u.current||u.current.readyState!=="open"||!o.current)return;const O=o.current,t=O.getBoundingClientRect(),n=O.videoWidth,_=O.videoHeight;if(!n||!_)return;const g=(A,K,k,h)=>{const F=t.width,v=t.height,$=n/_,se=F/v;let H=F,B=v;$>se?B=F/$:H=v*$;const ae=(F-H)/2,de=(v-B)/2,V=K-t.left-ae,G=k-t.top-de,x=V>=0&&V<=H&&G>=0&&G<=B;let J=0,q=0;x&&(J=Math.max(0,Math.min(n,V/H*n)),q=Math.max(0,Math.min(_,G/B*_)));let L=null,R=null,ue=1;const ee=b.BUTTON_PRIMARY;switch(h){case"down":x?(L=b.ACTION_DOWN,R={x:J,y:q},p.current.set(A,R),A===-1&&o.current?.focus()):p.current.delete(A);break;case"move":p.current.has(A)&&x&&(L=b.ACTION_MOVE,R={x:J,y:q},p.current.set(A,R));break;case"up":case"cancel":p.current.has(A)&&(L=h==="cancel"?b.ACTION_CANCEL:b.ACTION_UP,R=p.current.get(A),p.current.delete(A));break}if(L!==null&&R!==null){const te=De(L,A,n,_,R.x,R.y,ue,ee,ee);te&&U(te)}else(h==="up"||h==="cancel")&&p.current.delete(A)};if("touches"in r){const A=r.changedTouches;let K;switch(r.type){case"touchstart":K="down";break;case"touchmove":K="move";break;case"touchend":K="up";break;case"touchcancel":K="cancel";break;default:return}for(let k=0;k<A.length;k++){const h=A[k];g(h.identifier,h.clientX,h.clientY,K)}}else{let K=null;switch(r.type){case"mousedown":r.button===0&&(K="down");break;case"mousemove":p.current.has(-1)&&(K="move");break;case"mouseup":r.button===0&&(K="up");break;case"mouseleave":p.current.has(-1)&&(K="up");break}K&&g(-1,r.clientX,r.clientY,K)}},Q=r=>{if(r.preventDefault(),r.stopPropagation(),T("Keyboard event:",{type:r.type,key:r.key,keyCode:r.keyCode,code:r.code,target:r.target.tagName,focused:document.activeElement===o.current}),document.activeElement!==o.current){f("Video element not focused, skipping keyboard event");return}if(!u.current||u.current.readyState!=="open"){f("Data channel not ready for keyboard event:",u.current?.readyState);return}if(r.type==="keydown"){if(r.key.toLowerCase()==="v"&&(r.metaKey||r.ctrlKey)){T("Paste shortcut detected"),navigator.clipboard.readText().then(t=>{if(t){T("Pasting text via SET_CLIPBOARD:",t.substring(0,20)+(t.length>20?"...":""));const n=Ce(t,!0);U(n)}}).catch(t=>{console.error("Failed to read clipboard contents: ",t)});return}if(r.key.toLowerCase()==="m"&&(r.metaKey||r.ctrlKey)){T("Menu shortcut detected");const t=W(e.ACTION_DOWN,e.MENU,0,e.META_NONE);U(t);const n=W(e.ACTION_UP,e.MENU,0,e.META_NONE);U(n);return}}const O=Oe(r);if(O){const{keycode:t,metaState:n}=O,_=r.type==="keydown"?e.ACTION_DOWN:e.ACTION_UP;T(`Sending Keycode: key=${r.key}, code=${t}, action=${_}, meta=${n}`);const g=W(_,t,0,n);U(g)}else T(`Ignoring unhandled key event: type=${r.type}, key=${r.key}`)},oe=()=>{c.current&&c.current.readyState===WebSocket.OPEN&&c.current.send(JSON.stringify({type:"keepAlive",sessionId:m}))},X=()=>{I.current&&window.clearInterval(I.current),I.current=window.setInterval(oe,1e4)},Z=()=>{I.current&&(window.clearInterval(I.current),I.current=void 0)},z=()=>{document.hidden?Z():X()},ce=async()=>{try{c.current=new WebSocket(`${a}?token=${D}`),c.current.onerror=t=>{C("WebSocket error: "+t)},c.current.onclose=()=>{C("WebSocket closed")},await new Promise((t,n)=>{c.current&&(c.current.onopen=t,setTimeout(()=>n(new Error("WebSocket connection timeout")),5e3))});const O=await new Promise((t,n)=>{const _=setTimeout(()=>n(new Error("RTCConfiguration timeout")),5e3),g=A=>{try{const K=JSON.parse(A.data);K.type==="rtcConfiguration"&&(clearTimeout(_),c.current?.removeEventListener("message",g),t(K.rtcConfiguration))}catch(K){console.error("Error handling RTC configuration:",K),n(K)}};c.current?.addEventListener("message",g),c.current?.send(JSON.stringify({type:"requestRtcConfiguration",sessionId:m}))});if(s.current=new RTCPeerConnection(O),s.current.addTransceiver("audio",{direction:"recvonly"}),s.current.addTransceiver("video",{direction:"recvonly"}),u.current=s.current.createDataChannel("control",{ordered:!0,negotiated:!0,id:1}),u.current.onopen=()=>{if(C("Control channel opened"),c.current&&(c.current.send(JSON.stringify({type:"requestFrame",sessionId:m})),l))try{const t=decodeURIComponent(l);C("Opening URL"),c.current.send(JSON.stringify({type:"openUrl",url:t,sessionId:m}))}catch(t){console.error({error:t},"Error decoding URL, falling back to the original URL"),c.current.send(JSON.stringify({type:"openUrl",url:l,sessionId:m}))}},u.current.onclose=()=>{C("Control channel closed")},u.current.onerror=t=>{console.error("Control channel error:",t),C("Control channel error: "+t)},s.current.onconnectionstatechange=()=>{C("Connection state: "+s.current?.connectionState),d(s.current?.connectionState==="connected")},s.current.oniceconnectionstatechange=()=>{C("ICE state: "+s.current?.iceConnectionState)},s.current.ontrack=t=>{C("Received remote track: "+t.track.kind),t.track.kind==="video"&&o.current&&(T(`[${new Date().toISOString()}] Video track received:`,t.track),o.current.srcObject=t.streams[0])},s.current.onicecandidate=t=>{if(t.candidate&&c.current){const n={type:"candidate",candidate:t.candidate.candidate,sdpMid:t.candidate.sdpMid,sdpMLineIndex:t.candidate.sdpMLineIndex,sessionId:m};c.current.send(JSON.stringify(n)),C("Sent ICE candidate")}else C("ICE candidate gathering completed")},c.current.onmessage=async t=>{let n;try{n=JSON.parse(t.data)}catch(_){f("Error parsing message:",_);return}switch(C("Received: "+n.type),n.type){case"answer":if(!s.current){C("No peer connection, skipping answer");break}await s.current.setRemoteDescription(new RTCSessionDescription({type:"answer",sdp:n.sdp})),C("Set remote description");break;case"candidate":if(!s.current){C("No peer connection, skipping candidate");break}await s.current.addIceCandidate(new RTCIceCandidate({candidate:n.candidate,sdpMid:n.sdpMid,sdpMLineIndex:n.sdpMLineIndex})),C("Added ICE candidate");break;case"screenshot":if(typeof n.id!="string"||typeof n.dataUri!="string"){f("Received invalid screenshot success message:",n);break}const _=S.current.get(n.id);if(!_){f(`Received screenshot data for unknown or handled id: ${n.id}`);break}T(`Received screenshot data for id ${n.id}`),_({dataUri:n.dataUri}),S.current.delete(n.id),P.current.delete(n.id);break;case"screenshotError":if(typeof n.id!="string"||typeof n.message!="string"){f("Received invalid screenshot error message:",n);break}const g=P.current.get(n.id);if(!g){f(`Received screenshot error for unknown or handled id: ${n.id}`);break}f(`Received screenshot error for id ${n.id}: ${n.message}`),g(new Error(n.message)),S.current.delete(n.id),P.current.delete(n.id);break;default:f(`Received unhandled message type: ${n.type}`,n);break}},s.current){const t=await s.current.createOffer({offerToReceiveVideo:!0,offerToReceiveAudio:!1});await s.current.setLocalDescription(t),c.current&&c.current.send(JSON.stringify({type:"offer",sdp:t.sdp,sessionId:m})),C("Sent offer")}}catch(r){C("Error: "+r)}},Ee=()=>{c.current&&(c.current.close(),c.current=null),s.current&&(s.current.close(),s.current=null),o.current&&(o.current.srcObject=null),u.current&&(u.current.close(),u.current=null),d(!1),C("Stopped")};y.useEffect(()=>(ce(),document.hidden||X(),document.addEventListener("visibilitychange",z),()=>{Z(),Ee(),document.removeEventListener("visibilitychange",z)}),[a,D,i]);const ie=()=>{o.current&&o.current.focus()};return y.useImperativeHandle(Y,()=>({openUrl:r=>{if(!c.current||c.current.readyState!==WebSocket.OPEN){f("WebSocket not open, cannot send open_url command via ref.");return}try{const O=decodeURIComponent(r);C("Opening URL"),c.current.send(JSON.stringify({type:"openUrl",url:O,sessionId:m}))}catch(O){f("Error decoding or sending URL via ref:",{error:O,url:r}),c.current.send(JSON.stringify({type:"openUrl",url:r,sessionId:m}))}},sendKeyEvent:r=>{if(!u.current||u.current.readyState!=="open"){f("Data channel not ready for imperative key command:",u.current?.readyState);return}const O=re[r.code];if(!O){f(`Unknown event.code for imperative command: ${r.code}`);return}let t=e.META_NONE;r.shiftKey&&(t|=e.META_SHIFT_ON),r.altKey&&(t|=e.META_ALT_ON),r.ctrlKey&&(t|=e.META_CTRL_ON),r.metaKey&&(t|=e.META_META_ON);const n=r.type==="keydown"?e.ACTION_DOWN:e.ACTION_UP;T(`Sending Imperative Key Command: code=${r.code}, keycode=${O}, action=${n}, meta=${t}`);const _=W(n,O,0,t);_&&U(_)},screenshot:()=>new Promise((r,O)=>{if(!c.current||c.current.readyState!==WebSocket.OPEN)return f("WebSocket not open, cannot send screenshot command."),O(new Error("WebSocket is not connected or connection is not open."));const t=`ui-ss-${Date.now()}-${Math.random().toString(36).substring(2,9)}`,n={type:"screenshot",id:t};S.current.set(t,r),P.current.set(t,O),T("Sending screenshot request:",n);try{c.current.send(JSON.stringify(n))}catch(_){f("Failed to send screenshot request immediately:",_),S.current.delete(t),P.current.delete(t),O(_);return}setTimeout(()=>{S.current.has(t)&&(f(`Screenshot request timed out for id ${t}`),P.current.get(t)?.(new Error("Screenshot request timed out")),S.current.delete(t),P.current.delete(t))},3e4)})})),w.jsxs("div",{className:_e("rc-container",E),style:{touchAction:"none"},onMouseDown:M,onMouseMove:M,onMouseUp:M,onMouseLeave:M,onTouchStart:M,onTouchMove:M,onTouchEnd:M,onTouchCancel:M,children:[w.jsx("video",{ref:o,className:"rc-video",autoPlay:!0,playsInline:!0,muted:!0,tabIndex:0,style:{outline:"none",pointerEvents:"none"},onKeyDown:Q,onKeyUp:Q,onClick:ie,onFocus:()=>{o.current&&(o.current.style.outline="none")},onBlur:()=>{o.current&&(o.current.style.outline="none")}}),!N&&w.jsxs("div",{className:"rc-placeholder-wrapper",children:[w.jsx("div",{className:"rc-spinner"}),w.jsx("p",{className:"rc-placeholder-content",children:"Connecting..."})]})]})});exports.RemoteControl=Ke;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});require('./index.css');const b=require("react/jsx-runtime"),T=require("react");function ne(i){var u,D,s="";if(typeof i=="string"||typeof i=="number")s+=i;else if(typeof i=="object")if(Array.isArray(i)){var l=i.length;for(u=0;u<l;u++)i[u]&&(D=ne(i[u]))&&(s&&(s+=" "),s+=D)}else for(D in i)i[D]&&(s&&(s+=" "),s+=D);return s}function _e(){for(var i,u,D=0,s="",l=arguments.length;D<l;D++)(i=arguments[D])&&(u=ne(i))&&(s&&(s+=" "),s+=u);return s}const j={INJECT_KEYCODE:0,INJECT_TOUCH_EVENT:2,SET_CLIPBOARD:9},F={ACTION_DOWN:0,ACTION_UP:1,ACTION_MOVE:2,ACTION_CANCEL:3,BUTTON_PRIMARY:1},e={ACTION_DOWN:0,ACTION_UP:1,META_NONE:0,META_SHIFT_ON:1,META_ALT_ON:2,META_CTRL_ON:4096,META_META_ON:65536,KEYCODE_0:7,KEYCODE_1:8,KEYCODE_2:9,KEYCODE_3:10,KEYCODE_4:11,KEYCODE_5:12,KEYCODE_6:13,KEYCODE_7:14,KEYCODE_8:15,KEYCODE_9:16,DPAD_UP:19,DPAD_DOWN:20,DPAD_LEFT:21,DPAD_RIGHT:22,KEYCODE_A:29,KEYCODE_B:30,KEYCODE_C:31,KEYCODE_D:32,KEYCODE_E:33,KEYCODE_F:34,KEYCODE_G:35,KEYCODE_H:36,KEYCODE_I:37,KEYCODE_J:38,KEYCODE_K:39,KEYCODE_L:40,KEYCODE_M:41,KEYCODE_N:42,KEYCODE_O:43,KEYCODE_P:44,KEYCODE_Q:45,KEYCODE_R:46,KEYCODE_S:47,KEYCODE_T:48,KEYCODE_U:49,KEYCODE_V:50,KEYCODE_W:51,KEYCODE_X:52,KEYCODE_Y:53,KEYCODE_Z:54,KEYCODE_COMMA:55,KEYCODE_PERIOD:56,KEYCODE_ALT_LEFT:57,KEYCODE_ALT_RIGHT:58,KEYCODE_SHIFT_LEFT:59,KEYCODE_SHIFT_RIGHT:60,KEYCODE_TAB:61,KEYCODE_SPACE:62,ENTER:66,DEL:67,KEYCODE_GRAVE:68,KEYCODE_MINUS:69,KEYCODE_EQUALS:70,KEYCODE_LEFT_BRACKET:71,KEYCODE_RIGHT_BRACKET:72,KEYCODE_BACKSLASH:73,KEYCODE_SEMICOLON:74,KEYCODE_APOSTROPHE:75,KEYCODE_SLASH:76,MENU:82,KEYCODE_PAGE_UP:92,KEYCODE_PAGE_DOWN:93,KEYCODE_ESCAPE:111,FORWARD_DEL:112,KEYCODE_CTRL_LEFT:113,KEYCODE_CTRL_RIGHT:114,KEYCODE_META_LEFT:117,KEYCODE_META_RIGHT:118,KEYCODE_MOVE_HOME:122,KEYCODE_MOVE_END:123,KEYCODE_INSERT:124,KEYCODE_F1:131,KEYCODE_F2:132,KEYCODE_F3:133,KEYCODE_F4:134,KEYCODE_F5:135,KEYCODE_F6:136,KEYCODE_F7:137,KEYCODE_F8:138,KEYCODE_F9:139,KEYCODE_F10:140,KEYCODE_F11:141,KEYCODE_F12:142,KEYCODE_NUMPAD_0:144,KEYCODE_NUMPAD_1:145,KEYCODE_NUMPAD_2:146,KEYCODE_NUMPAD_3:147,KEYCODE_NUMPAD_4:148,KEYCODE_NUMPAD_5:149,KEYCODE_NUMPAD_6:150,KEYCODE_NUMPAD_7:151,KEYCODE_NUMPAD_8:152,KEYCODE_NUMPAD_9:153,KEYCODE_NUMPAD_DIVIDE:154,KEYCODE_NUMPAD_MULTIPLY:155,KEYCODE_NUMPAD_SUBTRACT:156,KEYCODE_NUMPAD_ADD:157,KEYCODE_NUMPAD_DOT:158,KEYCODE_NUMPAD_COMMA:159,KEYCODE_NUMPAD_ENTER:160,KEYCODE_NUMPAD_EQUALS:161},re={KeyA:e.KEYCODE_A,KeyB:e.KEYCODE_B,KeyC:e.KEYCODE_C,KeyD:e.KEYCODE_D,KeyE:e.KEYCODE_E,KeyF:e.KEYCODE_F,KeyG:e.KEYCODE_G,KeyH:e.KEYCODE_H,KeyI:e.KEYCODE_I,KeyJ:e.KEYCODE_J,KeyK:e.KEYCODE_K,KeyL:e.KEYCODE_L,KeyM:e.KEYCODE_M,KeyN:e.KEYCODE_N,KeyO:e.KEYCODE_O,KeyP:e.KEYCODE_P,KeyQ:e.KEYCODE_Q,KeyR:e.KEYCODE_R,KeyS:e.KEYCODE_S,KeyT:e.KEYCODE_T,KeyU:e.KEYCODE_U,KeyV:e.KEYCODE_V,KeyW:e.KEYCODE_W,KeyX:e.KEYCODE_X,KeyY:e.KEYCODE_Y,KeyZ:e.KEYCODE_Z,Digit0:e.KEYCODE_0,Digit1:e.KEYCODE_1,Digit2:e.KEYCODE_2,Digit3:e.KEYCODE_3,Digit4:e.KEYCODE_4,Digit5:e.KEYCODE_5,Digit6:e.KEYCODE_6,Digit7:e.KEYCODE_7,Digit8:e.KEYCODE_8,Digit9:e.KEYCODE_9,Backquote:e.KEYCODE_GRAVE,Minus:e.KEYCODE_MINUS,Equal:e.KEYCODE_EQUALS,BracketLeft:e.KEYCODE_LEFT_BRACKET,BracketRight:e.KEYCODE_RIGHT_BRACKET,Backslash:e.KEYCODE_BACKSLASH,Semicolon:e.KEYCODE_SEMICOLON,Quote:e.KEYCODE_APOSTROPHE,Comma:e.KEYCODE_COMMA,Period:e.KEYCODE_PERIOD,Slash:e.KEYCODE_SLASH,Space:e.KEYCODE_SPACE,Tab:e.KEYCODE_TAB,Escape:e.KEYCODE_ESCAPE,ArrowUp:e.DPAD_UP,ArrowDown:e.DPAD_DOWN,ArrowLeft:e.DPAD_LEFT,ArrowRight:e.DPAD_RIGHT,Enter:e.ENTER,Backspace:e.DEL,Delete:e.FORWARD_DEL,Home:e.KEYCODE_MOVE_HOME,End:e.KEYCODE_MOVE_END,PageUp:e.KEYCODE_PAGE_UP,PageDown:e.KEYCODE_PAGE_DOWN,Insert:e.KEYCODE_INSERT,F1:e.KEYCODE_F1,F2:e.KEYCODE_F2,F3:e.KEYCODE_F3,F4:e.KEYCODE_F4,F5:e.KEYCODE_F5,F6:e.KEYCODE_F6,F7:e.KEYCODE_F7,F8:e.KEYCODE_F8,F9:e.KEYCODE_F9,F10:e.KEYCODE_F10,F11:e.KEYCODE_F11,F12:e.KEYCODE_F12,ShiftLeft:e.KEYCODE_SHIFT_LEFT,ShiftRight:e.KEYCODE_SHIFT_RIGHT,ControlLeft:e.KEYCODE_CTRL_LEFT,ControlRight:e.KEYCODE_CTRL_RIGHT,AltLeft:e.KEYCODE_ALT_LEFT,AltRight:e.KEYCODE_ALT_RIGHT,MetaLeft:e.KEYCODE_META_LEFT,MetaRight:e.KEYCODE_META_RIGHT,ContextMenu:e.MENU,Numpad0:e.KEYCODE_NUMPAD_0,Numpad1:e.KEYCODE_NUMPAD_1,Numpad2:e.KEYCODE_NUMPAD_2,Numpad3:e.KEYCODE_NUMPAD_3,Numpad4:e.KEYCODE_NUMPAD_4,Numpad5:e.KEYCODE_NUMPAD_5,Numpad6:e.KEYCODE_NUMPAD_6,Numpad7:e.KEYCODE_NUMPAD_7,Numpad8:e.KEYCODE_NUMPAD_8,Numpad9:e.KEYCODE_NUMPAD_9,NumpadDivide:e.KEYCODE_NUMPAD_DIVIDE,NumpadMultiply:e.KEYCODE_NUMPAD_MULTIPLY,NumpadSubtract:e.KEYCODE_NUMPAD_SUBTRACT,NumpadAdd:e.KEYCODE_NUMPAD_ADD,NumpadDecimal:e.KEYCODE_NUMPAD_DOT,NumpadComma:e.KEYCODE_NUMPAD_COMMA,NumpadEnter:e.KEYCODE_NUMPAD_ENTER,NumpadEqual:e.KEYCODE_NUMPAD_EQUALS};function Ce(i,u,D,s,l,A,o=1,c=0,d=0){const C=new ArrayBuffer(32),N=new DataView(C);let _=0;return N.setUint8(_,j.INJECT_TOUCH_EVENT),_+=1,N.setUint8(_,i),_+=1,N.setBigInt64(_,BigInt(u)),_+=8,N.setInt32(_,Math.round(l),!0),_+=4,N.setInt32(_,Math.round(A),!0),_+=4,N.setUint16(_,D,!0),_+=2,N.setUint16(_,s,!0),_+=2,N.setInt16(_,Math.round(o*65535),!0),_+=2,N.setInt32(_,c,!0),_+=4,N.setInt32(_,d,!0),C}function De(i,u=!0){const s=new TextEncoder().encode(i),l=new ArrayBuffer(14+s.length),A=new DataView(l);let o=0;return A.setUint8(o,j.SET_CLIPBOARD),o+=1,A.setBigInt64(o,BigInt(0),!1),o+=8,A.setUint8(o,u?1:0),o+=1,A.setUint32(o,s.length,!1),o+=4,new Uint8Array(l,o).set(s),l}function W(i,u,D=0,s=0){const l=new ArrayBuffer(14),A=new DataView(l);let o=0;return A.setUint8(o,j.INJECT_KEYCODE),o+=1,A.setUint8(o,i),o+=1,A.setInt32(o,u,!0),o+=4,A.setInt32(o,D,!0),o+=4,A.setInt32(o,s,!0),l}const g=(...i)=>{window.debugRemoteControl&&console.log(...i)},Y=(...i)=>{window.debugRemoteControl&&console.warn(...i)};function Oe(i){const u=i.code,D=re[u];if(!D)return Y(`Unknown event.code: ${u}, key: ${i.key}`),null;let s=e.META_NONE;const l=u>="KeyA"&&u<="KeyZ",A=i.getModifierState("CapsLock"),o=i.shiftKey;let c=o;return l&&(c=o!==A),c&&(s|=e.META_SHIFT_ON),i.ctrlKey&&(s|=e.META_CTRL_ON),i.altKey&&(s|=e.META_ALT_ON),i.metaKey&&(s|=e.META_META_ON),{keycode:D,metaState:s}}const Ke=T.forwardRef(({className:i,url:u,token:D,sessionId:s,openUrl:l},A)=>{const o=T.useRef(null),c=T.useRef(null),d=T.useRef(null),C=T.useRef(null),[N,_]=T.useState(!1),k=T.useRef(void 0),I=T.useRef(new Map),U=T.useRef(new Map),m=T.useRef(new Map),R=T.useMemo(()=>s||Math.random().toString(36).substring(2,15)+Math.random().toString(36).substring(2,15),[s]),O=n=>{g(n)},L=n=>{!C.current||C.current.readyState!=="open"||C.current.send(n)},S=n=>{if(n.preventDefault(),n.stopPropagation(),!C.current||C.current.readyState!=="open"||!o.current)return;const K=o.current,E=K.getBoundingClientRect(),t=K.videoWidth,r=K.videoHeight;if(!t||!r)return;const y=(a,f,p,M)=>{const h=E.width,v=E.height,$=t/r,se=h/v;let H=h,B=v;$>se?B=h/$:H=v*$;const ae=(h-H)/2,de=(v-B)/2,V=f-E.left-ae,G=p-E.top-de,x=V>=0&&V<=H&&G>=0&&G<=B;let J=0,q=0;x&&(J=Math.max(0,Math.min(t,V/H*t)),q=Math.max(0,Math.min(r,G/B*r)));let w=null,P=null,ue=1;const ee=F.BUTTON_PRIMARY;switch(M){case"down":x?(w=F.ACTION_DOWN,P={x:J,y:q},m.current.set(a,P),a===-1&&o.current?.focus()):m.current.delete(a);break;case"move":m.current.has(a)&&x&&(w=F.ACTION_MOVE,P={x:J,y:q},m.current.set(a,P));break;case"up":case"cancel":m.current.has(a)&&(w=M==="cancel"?F.ACTION_CANCEL:F.ACTION_UP,P=m.current.get(a),m.current.delete(a));break}if(w!==null&&P!==null){const te=Ce(w,a,t,r,P.x,P.y,ue,ee,ee);te&&L(te)}else(M==="up"||M==="cancel")&&m.current.delete(a)};if("touches"in n){const a=n.changedTouches;let f;switch(n.type){case"touchstart":f="down";break;case"touchmove":f="move";break;case"touchend":f="up";break;case"touchcancel":f="cancel";break;default:return}for(let p=0;p<a.length;p++){const M=a[p];y(M.identifier,M.clientX,M.clientY,f)}}else{let f=null;switch(n.type){case"mousedown":n.button===0&&(f="down");break;case"mousemove":m.current.has(-1)&&(f="move");break;case"mouseup":n.button===0&&(f="up");break;case"mouseleave":m.current.has(-1)&&(f="up");break}f&&y(-1,n.clientX,n.clientY,f)}},Q=n=>{if(n.preventDefault(),n.stopPropagation(),g("Keyboard event:",{type:n.type,key:n.key,keyCode:n.keyCode,code:n.code,target:n.target.tagName,focused:document.activeElement===o.current}),document.activeElement!==o.current){Y("Video element not focused, skipping keyboard event");return}if(!C.current||C.current.readyState!=="open"){Y("Data channel not ready for keyboard event:",C.current?.readyState);return}if(n.type==="keydown"){if(n.key.toLowerCase()==="v"&&(n.metaKey||n.ctrlKey)){g("Paste shortcut detected"),navigator.clipboard.readText().then(E=>{if(E){g("Pasting text via SET_CLIPBOARD:",E.substring(0,20)+(E.length>20?"...":""));const t=De(E,!0);L(t)}}).catch(E=>{console.error("Failed to read clipboard contents: ",E)});return}if(n.key.toLowerCase()==="m"&&(n.metaKey||n.ctrlKey)){g("Menu shortcut detected");const E=W(e.ACTION_DOWN,e.MENU,0,e.META_NONE);L(E);const t=W(e.ACTION_UP,e.MENU,0,e.META_NONE);L(t);return}}const K=Oe(n);if(K){const{keycode:E,metaState:t}=K,r=n.type==="keydown"?e.ACTION_DOWN:e.ACTION_UP;g(`Sending Keycode: key=${n.key}, code=${E}, action=${r}, meta=${t}`);const y=W(r,E,0,t);L(y)}else g(`Ignoring unhandled key event: type=${n.type}, key=${n.key}`)},oe=()=>{c.current&&c.current.readyState===WebSocket.OPEN&&c.current.send(JSON.stringify({type:"keepAlive",sessionId:R}))},X=()=>{k.current&&window.clearInterval(k.current),k.current=window.setInterval(oe,1e4)},Z=()=>{k.current&&(window.clearInterval(k.current),k.current=void 0)},z=()=>{document.hidden?Z():X()},ce=async()=>{try{c.current=new WebSocket(`${u}?token=${D}`),c.current.onerror=t=>{O("WebSocket error: "+t)},c.current.onclose=()=>{O("WebSocket closed")},await new Promise((t,r)=>{c.current&&(c.current.onopen=t,setTimeout(()=>r(new Error("WebSocket connection timeout")),5e3))});const K=await new Promise((t,r)=>{const y=setTimeout(()=>r(new Error("RTCConfiguration timeout")),5e3),a=f=>{try{const p=JSON.parse(f.data);p.type==="rtcConfiguration"&&(clearTimeout(y),c.current?.removeEventListener("message",a),t(p.rtcConfiguration))}catch(p){console.error("Error handling RTC configuration:",p),r(p)}};c.current?.addEventListener("message",a),c.current?.send(JSON.stringify({type:"requestRtcConfiguration",sessionId:R}))});d.current=new RTCPeerConnection(K),d.current.addTransceiver("audio",{direction:"recvonly"});const E=d.current.addTransceiver("video",{direction:"recvonly"});if(RTCRtpReceiver.getCapabilities){const t=RTCRtpReceiver.getCapabilities("video");if(t&&t.codecs){const y=t.codecs.sort((a,f)=>{const p=M=>{const h=M.mimeType.toLowerCase();return h.includes("vp9")?1:h.includes("h265")||h.includes("hevc")?2:h.includes("h264")||h.includes("avc")?3:4};return p(a)-p(f)});E.setCodecPreferences(y),g("Set codec preferences:",y.map(a=>a.mimeType).join(", "))}}if(C.current=d.current.createDataChannel("control",{ordered:!0,negotiated:!0,id:1}),C.current.onopen=()=>{if(O("Control channel opened"),c.current){for(let t=0;t<12;t++)setTimeout(()=>{c.current&&c.current.send(JSON.stringify({type:"requestFrame",sessionId:R}))},t*125);if(l)try{const t=decodeURIComponent(l);O("Opening URL"),c.current.send(JSON.stringify({type:"openUrl",url:t,sessionId:R}))}catch(t){console.error({error:t},"Error decoding URL, falling back to the original URL"),c.current.send(JSON.stringify({type:"openUrl",url:l,sessionId:R}))}}},C.current.onclose=()=>{O("Control channel closed")},C.current.onerror=t=>{console.error("Control channel error:",t),O("Control channel error: "+t)},d.current.onconnectionstatechange=()=>{O("Connection state: "+d.current?.connectionState),_(d.current?.connectionState==="connected")},d.current.oniceconnectionstatechange=()=>{O("ICE state: "+d.current?.iceConnectionState)},d.current.ontrack=t=>{O("Received remote track: "+t.track.kind),t.track.kind==="video"&&o.current&&(g(`[${new Date().toISOString()}] Video track received:`,t.track),o.current.srcObject=t.streams[0])},d.current.onicecandidate=t=>{if(t.candidate&&c.current){const r={type:"candidate",candidate:t.candidate.candidate,sdpMid:t.candidate.sdpMid,sdpMLineIndex:t.candidate.sdpMLineIndex,sessionId:R};c.current.send(JSON.stringify(r)),O("Sent ICE candidate")}else O("ICE candidate gathering completed")},c.current.onmessage=async t=>{let r;try{r=JSON.parse(t.data)}catch(y){Y("Error parsing message:",y);return}switch(O("Received: "+r.type),r.type){case"answer":if(!d.current){O("No peer connection, skipping answer");break}await d.current.setRemoteDescription(new RTCSessionDescription({type:"answer",sdp:r.sdp})),O("Set remote description");break;case"candidate":if(!d.current){O("No peer connection, skipping candidate");break}await d.current.addIceCandidate(new RTCIceCandidate({candidate:r.candidate,sdpMid:r.sdpMid,sdpMLineIndex:r.sdpMLineIndex})),O("Added ICE candidate");break;case"screenshot":if(typeof r.id!="string"||typeof r.dataUri!="string"){Y("Received invalid screenshot success message:",r);break}const y=I.current.get(r.id);if(!y){Y(`Received screenshot data for unknown or handled id: ${r.id}`);break}g(`Received screenshot data for id ${r.id}`),y({dataUri:r.dataUri}),I.current.delete(r.id),U.current.delete(r.id);break;case"screenshotError":if(typeof r.id!="string"||typeof r.message!="string"){Y("Received invalid screenshot error message:",r);break}const a=U.current.get(r.id);if(!a){Y(`Received screenshot error for unknown or handled id: ${r.id}`);break}Y(`Received screenshot error for id ${r.id}: ${r.message}`),a(new Error(r.message)),I.current.delete(r.id),U.current.delete(r.id);break;default:Y(`Received unhandled message type: ${r.type}`,r);break}},d.current){const t=await d.current.createOffer({offerToReceiveVideo:!0,offerToReceiveAudio:!1});await d.current.setLocalDescription(t),c.current&&c.current.send(JSON.stringify({type:"offer",sdp:t.sdp,sessionId:R})),O("Sent offer")}}catch(n){O("Error: "+n)}},Ee=()=>{c.current&&(c.current.close(),c.current=null),d.current&&(d.current.close(),d.current=null),o.current&&(o.current.srcObject=null),C.current&&(C.current.close(),C.current=null),_(!1),O("Stopped")};T.useEffect(()=>(ce(),document.hidden||X(),document.addEventListener("visibilitychange",z),()=>{Z(),Ee(),document.removeEventListener("visibilitychange",z)}),[u,D,s]);const ie=()=>{o.current&&o.current.focus()};return T.useImperativeHandle(A,()=>({openUrl:n=>{if(!c.current||c.current.readyState!==WebSocket.OPEN){Y("WebSocket not open, cannot send open_url command via ref.");return}try{const K=decodeURIComponent(n);O("Opening URL"),c.current.send(JSON.stringify({type:"openUrl",url:K,sessionId:R}))}catch(K){Y("Error decoding or sending URL via ref:",{error:K,url:n}),c.current.send(JSON.stringify({type:"openUrl",url:n,sessionId:R}))}},sendKeyEvent:n=>{if(!C.current||C.current.readyState!=="open"){Y("Data channel not ready for imperative key command:",C.current?.readyState);return}const K=re[n.code];if(!K){Y(`Unknown event.code for imperative command: ${n.code}`);return}let E=e.META_NONE;n.shiftKey&&(E|=e.META_SHIFT_ON),n.altKey&&(E|=e.META_ALT_ON),n.ctrlKey&&(E|=e.META_CTRL_ON),n.metaKey&&(E|=e.META_META_ON);const t=n.type==="keydown"?e.ACTION_DOWN:e.ACTION_UP;g(`Sending Imperative Key Command: code=${n.code}, keycode=${K}, action=${t}, meta=${E}`);const r=W(t,K,0,E);r&&L(r)},screenshot:()=>new Promise((n,K)=>{if(!c.current||c.current.readyState!==WebSocket.OPEN)return Y("WebSocket not open, cannot send screenshot command."),K(new Error("WebSocket is not connected or connection is not open."));const E=`ui-ss-${Date.now()}-${Math.random().toString(36).substring(2,9)}`,t={type:"screenshot",id:E};I.current.set(E,n),U.current.set(E,K),g("Sending screenshot request:",t);try{c.current.send(JSON.stringify(t))}catch(r){Y("Failed to send screenshot request immediately:",r),I.current.delete(E),U.current.delete(E),K(r);return}setTimeout(()=>{I.current.has(E)&&(Y(`Screenshot request timed out for id ${E}`),U.current.get(E)?.(new Error("Screenshot request timed out")),I.current.delete(E),U.current.delete(E))},3e4)})})),b.jsxs("div",{className:_e("rc-container",i),style:{touchAction:"none"},onMouseDown:S,onMouseMove:S,onMouseUp:S,onMouseLeave:S,onTouchStart:S,onTouchMove:S,onTouchEnd:S,onTouchCancel:S,children:[b.jsx("video",{ref:o,className:"rc-video",autoPlay:!0,playsInline:!0,muted:!0,tabIndex:0,style:{outline:"none",pointerEvents:"none"},onKeyDown:Q,onKeyUp:Q,onClick:ie,onFocus:()=>{o.current&&(o.current.style.outline="none")},onBlur:()=>{o.current&&(o.current.style.outline="none")}}),!N&&b.jsxs("div",{className:"rc-placeholder-wrapper",children:[b.jsx("div",{className:"rc-spinner"}),b.jsx("p",{className:"rc-placeholder-content",children:"Connecting..."})]})]})});exports.RemoteControl=Ke;
package/dist/index.js CHANGED
@@ -1,23 +1,23 @@
1
1
  import { jsxs as ne, jsx as q } from "react/jsx-runtime";
2
- import { forwardRef as De, useRef as S, useState as Ce, useMemo as Oe, useEffect as Ke, useImperativeHandle as le } from "react";
3
- import './index.css';function re(E) {
4
- var a, D, i = "";
5
- if (typeof E == "string" || typeof E == "number") i += E;
6
- else if (typeof E == "object") if (Array.isArray(E)) {
7
- var l = E.length;
8
- for (a = 0; a < l; a++) E[a] && (D = re(E[a])) && (i && (i += " "), i += D);
9
- } else for (D in E) E[D] && (i && (i += " "), i += D);
10
- return i;
2
+ import { forwardRef as Ce, useRef as P, useState as De, useMemo as Oe, useEffect as Ke, useImperativeHandle as le } from "react";
3
+ import './index.css';function re(i) {
4
+ var u, D, s = "";
5
+ if (typeof i == "string" || typeof i == "number") s += i;
6
+ else if (typeof i == "object") if (Array.isArray(i)) {
7
+ var l = i.length;
8
+ for (u = 0; u < l; u++) i[u] && (D = re(i[u])) && (s && (s += " "), s += D);
9
+ } else for (D in i) i[D] && (s && (s += " "), s += D);
10
+ return s;
11
11
  }
12
- function Ye() {
13
- for (var E, a, D = 0, i = "", l = arguments.length; D < l; D++) (E = arguments[D]) && (a = re(E)) && (i && (i += " "), i += a);
14
- return i;
12
+ function fe() {
13
+ for (var i, u, D = 0, s = "", l = arguments.length; D < l; D++) (i = arguments[D]) && (u = re(i)) && (s && (s += " "), s += u);
14
+ return s;
15
15
  }
16
16
  const Q = {
17
17
  INJECT_KEYCODE: 0,
18
18
  INJECT_TOUCH_EVENT: 2,
19
19
  SET_CLIPBOARD: 9
20
- }, w = {
20
+ }, b = {
21
21
  ACTION_DOWN: 0,
22
22
  ACTION_UP: 1,
23
23
  ACTION_MOVE: 2,
@@ -256,449 +256,470 @@ const Q = {
256
256
  NumpadEnter: e.KEYCODE_NUMPAD_ENTER,
257
257
  NumpadEqual: e.KEYCODE_NUMPAD_EQUALS
258
258
  };
259
- function fe(E, a, D, i, l, f, o = 1, c = 0, s = 0) {
260
- const u = new ArrayBuffer(32), y = new DataView(u);
261
- let d = 0;
262
- return y.setUint8(d, Q.INJECT_TOUCH_EVENT), d += 1, y.setUint8(d, E), d += 1, y.setBigInt64(d, BigInt(a)), d += 8, y.setInt32(d, Math.round(l), !0), d += 4, y.setInt32(d, Math.round(f), !0), d += 4, y.setUint16(d, D, !0), d += 2, y.setUint16(d, i, !0), d += 2, y.setInt16(d, Math.round(o * 65535), !0), d += 2, y.setInt32(d, c, !0), d += 4, y.setInt32(d, s, !0), u;
259
+ function Ye(i, u, D, s, l, A, o = 1, c = 0, d = 0) {
260
+ const C = new ArrayBuffer(32), N = new DataView(C);
261
+ let _ = 0;
262
+ return N.setUint8(_, Q.INJECT_TOUCH_EVENT), _ += 1, N.setUint8(_, i), _ += 1, N.setBigInt64(_, BigInt(u)), _ += 8, N.setInt32(_, Math.round(l), !0), _ += 4, N.setInt32(_, Math.round(A), !0), _ += 4, N.setUint16(_, D, !0), _ += 2, N.setUint16(_, s, !0), _ += 2, N.setInt16(_, Math.round(o * 65535), !0), _ += 2, N.setInt32(_, c, !0), _ += 4, N.setInt32(_, d, !0), C;
263
263
  }
264
- function Ae(E, a = !0) {
265
- const i = new TextEncoder().encode(E), l = new ArrayBuffer(14 + i.length), f = new DataView(l);
264
+ function Ae(i, u = !0) {
265
+ const s = new TextEncoder().encode(i), l = new ArrayBuffer(14 + s.length), A = new DataView(l);
266
266
  let o = 0;
267
- return f.setUint8(o, Q.SET_CLIPBOARD), o += 1, f.setBigInt64(o, BigInt(0), !1), o += 8, f.setUint8(o, a ? 1 : 0), o += 1, f.setUint32(o, i.length, !1), o += 4, new Uint8Array(l, o).set(i), l;
267
+ return A.setUint8(o, Q.SET_CLIPBOARD), o += 1, A.setBigInt64(o, BigInt(0), !1), o += 8, A.setUint8(o, u ? 1 : 0), o += 1, A.setUint32(o, s.length, !1), o += 4, new Uint8Array(l, o).set(s), l;
268
268
  }
269
- function B(E, a, D = 0, i = 0) {
270
- const l = new ArrayBuffer(14), f = new DataView(l);
269
+ function B(i, u, D = 0, s = 0) {
270
+ const l = new ArrayBuffer(14), A = new DataView(l);
271
271
  let o = 0;
272
- return f.setUint8(o, Q.INJECT_KEYCODE), o += 1, f.setUint8(o, E), o += 1, f.setInt32(o, a, !0), o += 4, f.setInt32(o, D, !0), o += 4, f.setInt32(o, i, !0), l;
272
+ return A.setUint8(o, Q.INJECT_KEYCODE), o += 1, A.setUint8(o, i), o += 1, A.setInt32(o, u, !0), o += 4, A.setInt32(o, D, !0), o += 4, A.setInt32(o, s, !0), l;
273
273
  }
274
- const N = (...E) => {
275
- window.debugRemoteControl && console.log(...E);
276
- }, Y = (...E) => {
277
- window.debugRemoteControl && console.warn(...E);
274
+ const T = (...i) => {
275
+ window.debugRemoteControl && console.log(...i);
276
+ }, Y = (...i) => {
277
+ window.debugRemoteControl && console.warn(...i);
278
278
  };
279
- function ye(E) {
280
- const a = E.code, D = oe[a];
279
+ function pe(i) {
280
+ const u = i.code, D = oe[u];
281
281
  if (!D)
282
- return Y(`Unknown event.code: ${a}, key: ${E.key}`), null;
283
- let i = e.META_NONE;
284
- const l = a >= "KeyA" && a <= "KeyZ", f = E.getModifierState("CapsLock"), o = E.shiftKey;
282
+ return Y(`Unknown event.code: ${u}, key: ${i.key}`), null;
283
+ let s = e.META_NONE;
284
+ const l = u >= "KeyA" && u <= "KeyZ", A = i.getModifierState("CapsLock"), o = i.shiftKey;
285
285
  let c = o;
286
- return l && (c = o !== f), c && (i |= e.META_SHIFT_ON), E.ctrlKey && (i |= e.META_CTRL_ON), E.altKey && (i |= e.META_ALT_ON), E.metaKey && (i |= e.META_META_ON), { keycode: D, metaState: i };
286
+ return l && (c = o !== A), c && (s |= e.META_SHIFT_ON), i.ctrlKey && (s |= e.META_CTRL_ON), i.altKey && (s |= e.META_ALT_ON), i.metaKey && (s |= e.META_META_ON), { keycode: D, metaState: s };
287
287
  }
288
- const Te = De(
289
- ({ className: E, url: a, token: D, sessionId: i, openUrl: l }, f) => {
290
- const o = S(null), c = S(null), s = S(null), u = S(null), [y, d] = Ce(!1), P = S(void 0), R = S(/* @__PURE__ */ new Map()), I = S(/* @__PURE__ */ new Map()), p = S(/* @__PURE__ */ new Map()), g = Oe(
291
- () => i || Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15),
292
- [i]
293
- ), C = (r) => {
294
- N(r);
295
- }, U = (r) => {
296
- !u.current || u.current.readyState !== "open" || u.current.send(r);
297
- }, m = (r) => {
298
- if (r.preventDefault(), r.stopPropagation(), !u.current || u.current.readyState !== "open" || !o.current)
288
+ const Te = Ce(
289
+ ({ className: i, url: u, token: D, sessionId: s, openUrl: l }, A) => {
290
+ const o = P(null), c = P(null), d = P(null), C = P(null), [N, _] = De(!1), k = P(void 0), I = P(/* @__PURE__ */ new Map()), U = P(/* @__PURE__ */ new Map()), m = P(/* @__PURE__ */ new Map()), h = Oe(
291
+ () => s || Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15),
292
+ [s]
293
+ ), O = (n) => {
294
+ T(n);
295
+ }, L = (n) => {
296
+ !C.current || C.current.readyState !== "open" || C.current.send(n);
297
+ }, S = (n) => {
298
+ if (n.preventDefault(), n.stopPropagation(), !C.current || C.current.readyState !== "open" || !o.current)
299
299
  return;
300
- const O = o.current, t = O.getBoundingClientRect(), n = O.videoWidth, _ = O.videoHeight;
301
- if (!n || !_) return;
302
- const T = (A, K, k, M) => {
303
- const b = t.width, F = t.height, W = n / _, ae = b / F;
304
- let v = b, H = F;
305
- W > ae ? H = b / W : v = F * W;
306
- const de = (b - v) / 2, ue = (F - H) / 2, $ = K - t.left - de, V = k - t.top - ue, G = $ >= 0 && $ <= v && V >= 0 && V <= H;
300
+ const K = o.current, E = K.getBoundingClientRect(), t = K.videoWidth, r = K.videoHeight;
301
+ if (!t || !r) return;
302
+ const p = (a, f, y, g) => {
303
+ const M = E.width, F = E.height, W = t / r, ae = M / F;
304
+ let v = M, H = F;
305
+ W > ae ? H = M / W : v = F * W;
306
+ const de = (M - v) / 2, ue = (F - H) / 2, $ = f - E.left - de, V = y - E.top - ue, G = $ >= 0 && $ <= v && V >= 0 && V <= H;
307
307
  let J = 0, x = 0;
308
- G && (J = Math.max(0, Math.min(n, $ / v * n)), x = Math.max(0, Math.min(_, V / H * _)));
309
- let L = null, h = null, _e = 1;
310
- const ee = w.BUTTON_PRIMARY;
311
- switch (M) {
308
+ G && (J = Math.max(0, Math.min(t, $ / v * t)), x = Math.max(0, Math.min(r, V / H * r)));
309
+ let w = null, R = null, _e = 1;
310
+ const ee = b.BUTTON_PRIMARY;
311
+ switch (g) {
312
312
  case "down":
313
- G ? (L = w.ACTION_DOWN, h = { x: J, y: x }, p.current.set(A, h), A === -1 && o.current?.focus()) : p.current.delete(A);
313
+ G ? (w = b.ACTION_DOWN, R = { x: J, y: x }, m.current.set(a, R), a === -1 && o.current?.focus()) : m.current.delete(a);
314
314
  break;
315
315
  case "move":
316
- p.current.has(A) && G && (L = w.ACTION_MOVE, h = { x: J, y: x }, p.current.set(A, h));
316
+ m.current.has(a) && G && (w = b.ACTION_MOVE, R = { x: J, y: x }, m.current.set(a, R));
317
317
  break;
318
318
  case "up":
319
319
  case "cancel":
320
- p.current.has(A) && (L = M === "cancel" ? w.ACTION_CANCEL : w.ACTION_UP, h = p.current.get(A), p.current.delete(A));
320
+ m.current.has(a) && (w = g === "cancel" ? b.ACTION_CANCEL : b.ACTION_UP, R = m.current.get(a), m.current.delete(a));
321
321
  break;
322
322
  }
323
- if (L !== null && h !== null) {
324
- const te = fe(
325
- L,
326
- A,
327
- n,
328
- _,
329
- h.x,
330
- h.y,
323
+ if (w !== null && R !== null) {
324
+ const te = Ye(
325
+ w,
326
+ a,
327
+ t,
328
+ r,
329
+ R.x,
330
+ R.y,
331
331
  _e,
332
332
  ee,
333
333
  ee
334
334
  );
335
- te && U(te);
336
- } else (M === "up" || M === "cancel") && p.current.delete(A);
335
+ te && L(te);
336
+ } else (g === "up" || g === "cancel") && m.current.delete(a);
337
337
  };
338
- if ("touches" in r) {
339
- const A = r.changedTouches;
340
- let K;
341
- switch (r.type) {
338
+ if ("touches" in n) {
339
+ const a = n.changedTouches;
340
+ let f;
341
+ switch (n.type) {
342
342
  case "touchstart":
343
- K = "down";
343
+ f = "down";
344
344
  break;
345
345
  case "touchmove":
346
- K = "move";
346
+ f = "move";
347
347
  break;
348
348
  case "touchend":
349
- K = "up";
349
+ f = "up";
350
350
  break;
351
351
  case "touchcancel":
352
- K = "cancel";
352
+ f = "cancel";
353
353
  break;
354
354
  default:
355
355
  return;
356
356
  }
357
- for (let k = 0; k < A.length; k++) {
358
- const M = A[k];
359
- T(M.identifier, M.clientX, M.clientY, K);
357
+ for (let y = 0; y < a.length; y++) {
358
+ const g = a[y];
359
+ p(g.identifier, g.clientX, g.clientY, f);
360
360
  }
361
361
  } else {
362
- let K = null;
363
- switch (r.type) {
362
+ let f = null;
363
+ switch (n.type) {
364
364
  case "mousedown":
365
- r.button === 0 && (K = "down");
365
+ n.button === 0 && (f = "down");
366
366
  break;
367
367
  case "mousemove":
368
- p.current.has(-1) && (K = "move");
368
+ m.current.has(-1) && (f = "move");
369
369
  break;
370
370
  case "mouseup":
371
- r.button === 0 && (K = "up");
371
+ n.button === 0 && (f = "up");
372
372
  break;
373
373
  case "mouseleave":
374
- p.current.has(-1) && (K = "up");
374
+ m.current.has(-1) && (f = "up");
375
375
  break;
376
376
  }
377
- K && T(-1, r.clientX, r.clientY, K);
377
+ f && p(-1, n.clientX, n.clientY, f);
378
378
  }
379
- }, X = (r) => {
380
- if (r.preventDefault(), r.stopPropagation(), N("Keyboard event:", {
381
- type: r.type,
382
- key: r.key,
383
- keyCode: r.keyCode,
384
- code: r.code,
385
- target: r.target.tagName,
379
+ }, X = (n) => {
380
+ if (n.preventDefault(), n.stopPropagation(), T("Keyboard event:", {
381
+ type: n.type,
382
+ key: n.key,
383
+ keyCode: n.keyCode,
384
+ code: n.code,
385
+ target: n.target.tagName,
386
386
  focused: document.activeElement === o.current
387
387
  }), document.activeElement !== o.current) {
388
388
  Y("Video element not focused, skipping keyboard event");
389
389
  return;
390
390
  }
391
- if (!u.current || u.current.readyState !== "open") {
392
- Y("Data channel not ready for keyboard event:", u.current?.readyState);
391
+ if (!C.current || C.current.readyState !== "open") {
392
+ Y("Data channel not ready for keyboard event:", C.current?.readyState);
393
393
  return;
394
394
  }
395
- if (r.type === "keydown") {
396
- if (r.key.toLowerCase() === "v" && (r.metaKey || r.ctrlKey)) {
397
- N("Paste shortcut detected"), navigator.clipboard.readText().then((t) => {
398
- if (t) {
399
- N(
395
+ if (n.type === "keydown") {
396
+ if (n.key.toLowerCase() === "v" && (n.metaKey || n.ctrlKey)) {
397
+ T("Paste shortcut detected"), navigator.clipboard.readText().then((E) => {
398
+ if (E) {
399
+ T(
400
400
  "Pasting text via SET_CLIPBOARD:",
401
- t.substring(0, 20) + (t.length > 20 ? "..." : "")
401
+ E.substring(0, 20) + (E.length > 20 ? "..." : "")
402
402
  );
403
- const n = Ae(t, !0);
404
- U(n);
403
+ const t = Ae(E, !0);
404
+ L(t);
405
405
  }
406
- }).catch((t) => {
407
- console.error("Failed to read clipboard contents: ", t);
406
+ }).catch((E) => {
407
+ console.error("Failed to read clipboard contents: ", E);
408
408
  });
409
409
  return;
410
410
  }
411
- if (r.key.toLowerCase() === "m" && (r.metaKey || r.ctrlKey)) {
412
- N("Menu shortcut detected");
413
- const t = B(
411
+ if (n.key.toLowerCase() === "m" && (n.metaKey || n.ctrlKey)) {
412
+ T("Menu shortcut detected");
413
+ const E = B(
414
414
  e.ACTION_DOWN,
415
415
  e.MENU,
416
416
  0,
417
417
  e.META_NONE
418
418
  // Modifiers are handled by the shortcut check, not passed down
419
419
  );
420
- U(t);
421
- const n = B(
420
+ L(E);
421
+ const t = B(
422
422
  e.ACTION_UP,
423
423
  e.MENU,
424
424
  0,
425
425
  e.META_NONE
426
426
  );
427
- U(n);
427
+ L(t);
428
428
  return;
429
429
  }
430
430
  }
431
- const O = ye(r);
432
- if (O) {
433
- const { keycode: t, metaState: n } = O, _ = r.type === "keydown" ? e.ACTION_DOWN : e.ACTION_UP;
434
- N(`Sending Keycode: key=${r.key}, code=${t}, action=${_}, meta=${n}`);
435
- const T = B(
436
- _,
437
- t,
431
+ const K = pe(n);
432
+ if (K) {
433
+ const { keycode: E, metaState: t } = K, r = n.type === "keydown" ? e.ACTION_DOWN : e.ACTION_UP;
434
+ T(`Sending Keycode: key=${n.key}, code=${E}, action=${r}, meta=${t}`);
435
+ const p = B(
436
+ r,
437
+ E,
438
438
  0,
439
439
  // repeat count, typically 0 for single presses
440
- n
440
+ t
441
441
  );
442
- U(T);
442
+ L(p);
443
443
  } else
444
- N(`Ignoring unhandled key event: type=${r.type}, key=${r.key}`);
444
+ T(`Ignoring unhandled key event: type=${n.type}, key=${n.key}`);
445
445
  }, ce = () => {
446
446
  c.current && c.current.readyState === WebSocket.OPEN && c.current.send(
447
447
  JSON.stringify({
448
448
  type: "keepAlive",
449
- sessionId: g
449
+ sessionId: h
450
450
  })
451
451
  );
452
452
  }, j = () => {
453
- P.current && window.clearInterval(P.current), P.current = window.setInterval(ce, 1e4);
453
+ k.current && window.clearInterval(k.current), k.current = window.setInterval(ce, 1e4);
454
454
  }, Z = () => {
455
- P.current && (window.clearInterval(P.current), P.current = void 0);
455
+ k.current && (window.clearInterval(k.current), k.current = void 0);
456
456
  }, z = () => {
457
457
  document.hidden ? Z() : j();
458
458
  }, Ee = async () => {
459
459
  try {
460
- c.current = new WebSocket(`${a}?token=${D}`), c.current.onerror = (t) => {
461
- C("WebSocket error: " + t);
460
+ c.current = new WebSocket(`${u}?token=${D}`), c.current.onerror = (t) => {
461
+ O("WebSocket error: " + t);
462
462
  }, c.current.onclose = () => {
463
- C("WebSocket closed");
464
- }, await new Promise((t, n) => {
465
- c.current && (c.current.onopen = t, setTimeout(() => n(new Error("WebSocket connection timeout")), 5e3));
463
+ O("WebSocket closed");
464
+ }, await new Promise((t, r) => {
465
+ c.current && (c.current.onopen = t, setTimeout(() => r(new Error("WebSocket connection timeout")), 5e3));
466
466
  });
467
- const O = await new Promise((t, n) => {
468
- const _ = setTimeout(() => n(new Error("RTCConfiguration timeout")), 5e3), T = (A) => {
467
+ const K = await new Promise((t, r) => {
468
+ const p = setTimeout(() => r(new Error("RTCConfiguration timeout")), 5e3), a = (f) => {
469
469
  try {
470
- const K = JSON.parse(A.data);
471
- K.type === "rtcConfiguration" && (clearTimeout(_), c.current?.removeEventListener("message", T), t(K.rtcConfiguration));
472
- } catch (K) {
473
- console.error("Error handling RTC configuration:", K), n(K);
470
+ const y = JSON.parse(f.data);
471
+ y.type === "rtcConfiguration" && (clearTimeout(p), c.current?.removeEventListener("message", a), t(y.rtcConfiguration));
472
+ } catch (y) {
473
+ console.error("Error handling RTC configuration:", y), r(y);
474
474
  }
475
475
  };
476
- c.current?.addEventListener("message", T), c.current?.send(
476
+ c.current?.addEventListener("message", a), c.current?.send(
477
477
  JSON.stringify({
478
478
  type: "requestRtcConfiguration",
479
- sessionId: g
479
+ sessionId: h
480
480
  })
481
481
  );
482
482
  });
483
- if (s.current = new RTCPeerConnection(O), s.current.addTransceiver("audio", { direction: "recvonly" }), s.current.addTransceiver("video", { direction: "recvonly" }), u.current = s.current.createDataChannel("control", {
483
+ d.current = new RTCPeerConnection(K), d.current.addTransceiver("audio", { direction: "recvonly" });
484
+ const E = d.current.addTransceiver("video", { direction: "recvonly" });
485
+ if (RTCRtpReceiver.getCapabilities) {
486
+ const t = RTCRtpReceiver.getCapabilities("video");
487
+ if (t && t.codecs) {
488
+ const p = t.codecs.sort((a, f) => {
489
+ const y = (g) => {
490
+ const M = g.mimeType.toLowerCase();
491
+ return M.includes("vp9") ? 1 : M.includes("h265") || M.includes("hevc") ? 2 : M.includes("h264") || M.includes("avc") ? 3 : 4;
492
+ };
493
+ return y(a) - y(f);
494
+ });
495
+ E.setCodecPreferences(p), T("Set codec preferences:", p.map((a) => a.mimeType).join(", "));
496
+ }
497
+ }
498
+ if (C.current = d.current.createDataChannel("control", {
484
499
  ordered: !0,
485
500
  negotiated: !0,
486
501
  id: 1
487
- }), u.current.onopen = () => {
488
- if (C("Control channel opened"), c.current && (c.current.send(JSON.stringify({ type: "requestFrame", sessionId: g })), l))
489
- try {
490
- const t = decodeURIComponent(l);
491
- C("Opening URL"), c.current.send(
492
- JSON.stringify({
493
- type: "openUrl",
494
- url: t,
495
- sessionId: g
496
- })
497
- );
498
- } catch (t) {
499
- console.error({ error: t }, "Error decoding URL, falling back to the original URL"), c.current.send(
500
- JSON.stringify({
501
- type: "openUrl",
502
- url: l,
503
- sessionId: g
504
- })
505
- );
506
- }
507
- }, u.current.onclose = () => {
508
- C("Control channel closed");
509
- }, u.current.onerror = (t) => {
510
- console.error("Control channel error:", t), C("Control channel error: " + t);
511
- }, s.current.onconnectionstatechange = () => {
512
- C("Connection state: " + s.current?.connectionState), d(s.current?.connectionState === "connected");
513
- }, s.current.oniceconnectionstatechange = () => {
514
- C("ICE state: " + s.current?.iceConnectionState);
515
- }, s.current.ontrack = (t) => {
516
- C("Received remote track: " + t.track.kind), t.track.kind === "video" && o.current && (N(`[${(/* @__PURE__ */ new Date()).toISOString()}] Video track received:`, t.track), o.current.srcObject = t.streams[0]);
517
- }, s.current.onicecandidate = (t) => {
502
+ }), C.current.onopen = () => {
503
+ if (O("Control channel opened"), c.current) {
504
+ for (let t = 0; t < 12; t++)
505
+ setTimeout(() => {
506
+ c.current && c.current.send(JSON.stringify({ type: "requestFrame", sessionId: h }));
507
+ }, t * 125);
508
+ if (l)
509
+ try {
510
+ const t = decodeURIComponent(l);
511
+ O("Opening URL"), c.current.send(
512
+ JSON.stringify({
513
+ type: "openUrl",
514
+ url: t,
515
+ sessionId: h
516
+ })
517
+ );
518
+ } catch (t) {
519
+ console.error({ error: t }, "Error decoding URL, falling back to the original URL"), c.current.send(
520
+ JSON.stringify({
521
+ type: "openUrl",
522
+ url: l,
523
+ sessionId: h
524
+ })
525
+ );
526
+ }
527
+ }
528
+ }, C.current.onclose = () => {
529
+ O("Control channel closed");
530
+ }, C.current.onerror = (t) => {
531
+ console.error("Control channel error:", t), O("Control channel error: " + t);
532
+ }, d.current.onconnectionstatechange = () => {
533
+ O("Connection state: " + d.current?.connectionState), _(d.current?.connectionState === "connected");
534
+ }, d.current.oniceconnectionstatechange = () => {
535
+ O("ICE state: " + d.current?.iceConnectionState);
536
+ }, d.current.ontrack = (t) => {
537
+ O("Received remote track: " + t.track.kind), t.track.kind === "video" && o.current && (T(`[${(/* @__PURE__ */ new Date()).toISOString()}] Video track received:`, t.track), o.current.srcObject = t.streams[0]);
538
+ }, d.current.onicecandidate = (t) => {
518
539
  if (t.candidate && c.current) {
519
- const n = {
540
+ const r = {
520
541
  type: "candidate",
521
542
  candidate: t.candidate.candidate,
522
543
  sdpMid: t.candidate.sdpMid,
523
544
  sdpMLineIndex: t.candidate.sdpMLineIndex,
524
- sessionId: g
545
+ sessionId: h
525
546
  };
526
- c.current.send(JSON.stringify(n)), C("Sent ICE candidate");
547
+ c.current.send(JSON.stringify(r)), O("Sent ICE candidate");
527
548
  } else
528
- C("ICE candidate gathering completed");
549
+ O("ICE candidate gathering completed");
529
550
  }, c.current.onmessage = async (t) => {
530
- let n;
551
+ let r;
531
552
  try {
532
- n = JSON.parse(t.data);
533
- } catch (_) {
534
- Y("Error parsing message:", _);
553
+ r = JSON.parse(t.data);
554
+ } catch (p) {
555
+ Y("Error parsing message:", p);
535
556
  return;
536
557
  }
537
- switch (C("Received: " + n.type), n.type) {
558
+ switch (O("Received: " + r.type), r.type) {
538
559
  case "answer":
539
- if (!s.current) {
540
- C("No peer connection, skipping answer");
560
+ if (!d.current) {
561
+ O("No peer connection, skipping answer");
541
562
  break;
542
563
  }
543
- await s.current.setRemoteDescription(
564
+ await d.current.setRemoteDescription(
544
565
  new RTCSessionDescription({
545
566
  type: "answer",
546
- sdp: n.sdp
567
+ sdp: r.sdp
547
568
  })
548
- ), C("Set remote description");
569
+ ), O("Set remote description");
549
570
  break;
550
571
  case "candidate":
551
- if (!s.current) {
552
- C("No peer connection, skipping candidate");
572
+ if (!d.current) {
573
+ O("No peer connection, skipping candidate");
553
574
  break;
554
575
  }
555
- await s.current.addIceCandidate(
576
+ await d.current.addIceCandidate(
556
577
  new RTCIceCandidate({
557
- candidate: n.candidate,
558
- sdpMid: n.sdpMid,
559
- sdpMLineIndex: n.sdpMLineIndex
578
+ candidate: r.candidate,
579
+ sdpMid: r.sdpMid,
580
+ sdpMLineIndex: r.sdpMLineIndex
560
581
  })
561
- ), C("Added ICE candidate");
582
+ ), O("Added ICE candidate");
562
583
  break;
563
584
  case "screenshot":
564
- if (typeof n.id != "string" || typeof n.dataUri != "string") {
565
- Y("Received invalid screenshot success message:", n);
585
+ if (typeof r.id != "string" || typeof r.dataUri != "string") {
586
+ Y("Received invalid screenshot success message:", r);
566
587
  break;
567
588
  }
568
- const _ = R.current.get(n.id);
569
- if (!_) {
570
- Y(`Received screenshot data for unknown or handled id: ${n.id}`);
589
+ const p = I.current.get(r.id);
590
+ if (!p) {
591
+ Y(`Received screenshot data for unknown or handled id: ${r.id}`);
571
592
  break;
572
593
  }
573
- N(`Received screenshot data for id ${n.id}`), _({ dataUri: n.dataUri }), R.current.delete(n.id), I.current.delete(n.id);
594
+ T(`Received screenshot data for id ${r.id}`), p({ dataUri: r.dataUri }), I.current.delete(r.id), U.current.delete(r.id);
574
595
  break;
575
596
  case "screenshotError":
576
- if (typeof n.id != "string" || typeof n.message != "string") {
577
- Y("Received invalid screenshot error message:", n);
597
+ if (typeof r.id != "string" || typeof r.message != "string") {
598
+ Y("Received invalid screenshot error message:", r);
578
599
  break;
579
600
  }
580
- const T = I.current.get(n.id);
581
- if (!T) {
582
- Y(`Received screenshot error for unknown or handled id: ${n.id}`);
601
+ const a = U.current.get(r.id);
602
+ if (!a) {
603
+ Y(`Received screenshot error for unknown or handled id: ${r.id}`);
583
604
  break;
584
605
  }
585
- Y(`Received screenshot error for id ${n.id}: ${n.message}`), T(new Error(n.message)), R.current.delete(n.id), I.current.delete(n.id);
606
+ Y(`Received screenshot error for id ${r.id}: ${r.message}`), a(new Error(r.message)), I.current.delete(r.id), U.current.delete(r.id);
586
607
  break;
587
608
  default:
588
- Y(`Received unhandled message type: ${n.type}`, n);
609
+ Y(`Received unhandled message type: ${r.type}`, r);
589
610
  break;
590
611
  }
591
- }, s.current) {
592
- const t = await s.current.createOffer({
612
+ }, d.current) {
613
+ const t = await d.current.createOffer({
593
614
  offerToReceiveVideo: !0,
594
615
  offerToReceiveAudio: !1
595
616
  });
596
- await s.current.setLocalDescription(t), c.current && c.current.send(
617
+ await d.current.setLocalDescription(t), c.current && c.current.send(
597
618
  JSON.stringify({
598
619
  type: "offer",
599
620
  sdp: t.sdp,
600
- sessionId: g
621
+ sessionId: h
601
622
  })
602
- ), C("Sent offer");
623
+ ), O("Sent offer");
603
624
  }
604
- } catch (r) {
605
- C("Error: " + r);
625
+ } catch (n) {
626
+ O("Error: " + n);
606
627
  }
607
628
  }, ie = () => {
608
- c.current && (c.current.close(), c.current = null), s.current && (s.current.close(), s.current = null), o.current && (o.current.srcObject = null), u.current && (u.current.close(), u.current = null), d(!1), C("Stopped");
629
+ c.current && (c.current.close(), c.current = null), d.current && (d.current.close(), d.current = null), o.current && (o.current.srcObject = null), C.current && (C.current.close(), C.current = null), _(!1), O("Stopped");
609
630
  };
610
631
  Ke(() => (Ee(), document.hidden || j(), document.addEventListener("visibilitychange", z), () => {
611
632
  Z(), ie(), document.removeEventListener("visibilitychange", z);
612
- }), [a, D, i]);
633
+ }), [u, D, s]);
613
634
  const se = () => {
614
635
  o.current && o.current.focus();
615
636
  };
616
- return le(f, () => ({
617
- openUrl: (r) => {
637
+ return le(A, () => ({
638
+ openUrl: (n) => {
618
639
  if (!c.current || c.current.readyState !== WebSocket.OPEN) {
619
640
  Y("WebSocket not open, cannot send open_url command via ref.");
620
641
  return;
621
642
  }
622
643
  try {
623
- const O = decodeURIComponent(r);
624
- C("Opening URL"), c.current.send(
644
+ const K = decodeURIComponent(n);
645
+ O("Opening URL"), c.current.send(
625
646
  JSON.stringify({
626
647
  type: "openUrl",
627
- url: O,
628
- sessionId: g
648
+ url: K,
649
+ sessionId: h
629
650
  })
630
651
  );
631
- } catch (O) {
632
- Y("Error decoding or sending URL via ref:", { error: O, url: r }), c.current.send(
652
+ } catch (K) {
653
+ Y("Error decoding or sending URL via ref:", { error: K, url: n }), c.current.send(
633
654
  JSON.stringify({
634
655
  type: "openUrl",
635
- url: r,
636
- sessionId: g
656
+ url: n,
657
+ sessionId: h
637
658
  })
638
659
  );
639
660
  }
640
661
  },
641
- sendKeyEvent: (r) => {
642
- if (!u.current || u.current.readyState !== "open") {
643
- Y("Data channel not ready for imperative key command:", u.current?.readyState);
662
+ sendKeyEvent: (n) => {
663
+ if (!C.current || C.current.readyState !== "open") {
664
+ Y("Data channel not ready for imperative key command:", C.current?.readyState);
644
665
  return;
645
666
  }
646
- const O = oe[r.code];
647
- if (!O) {
648
- Y(`Unknown event.code for imperative command: ${r.code}`);
667
+ const K = oe[n.code];
668
+ if (!K) {
669
+ Y(`Unknown event.code for imperative command: ${n.code}`);
649
670
  return;
650
671
  }
651
- let t = e.META_NONE;
652
- r.shiftKey && (t |= e.META_SHIFT_ON), r.altKey && (t |= e.META_ALT_ON), r.ctrlKey && (t |= e.META_CTRL_ON), r.metaKey && (t |= e.META_META_ON);
653
- const n = r.type === "keydown" ? e.ACTION_DOWN : e.ACTION_UP;
654
- N(
655
- `Sending Imperative Key Command: code=${r.code}, keycode=${O}, action=${n}, meta=${t}`
672
+ let E = e.META_NONE;
673
+ n.shiftKey && (E |= e.META_SHIFT_ON), n.altKey && (E |= e.META_ALT_ON), n.ctrlKey && (E |= e.META_CTRL_ON), n.metaKey && (E |= e.META_META_ON);
674
+ const t = n.type === "keydown" ? e.ACTION_DOWN : e.ACTION_UP;
675
+ T(
676
+ `Sending Imperative Key Command: code=${n.code}, keycode=${K}, action=${t}, meta=${E}`
656
677
  );
657
- const _ = B(
658
- n,
659
- O,
678
+ const r = B(
679
+ t,
680
+ K,
660
681
  0,
661
682
  // repeat count, typically 0 for single presses
662
- t
683
+ E
663
684
  );
664
- _ && U(_);
685
+ r && L(r);
665
686
  },
666
- screenshot: () => new Promise((r, O) => {
687
+ screenshot: () => new Promise((n, K) => {
667
688
  if (!c.current || c.current.readyState !== WebSocket.OPEN)
668
- return Y("WebSocket not open, cannot send screenshot command."), O(new Error("WebSocket is not connected or connection is not open."));
669
- const t = `ui-ss-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, n = {
689
+ return Y("WebSocket not open, cannot send screenshot command."), K(new Error("WebSocket is not connected or connection is not open."));
690
+ const E = `ui-ss-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, t = {
670
691
  type: "screenshot",
671
692
  // Matches the type expected by instance API
672
- id: t
693
+ id: E
673
694
  };
674
- R.current.set(t, r), I.current.set(t, O), N("Sending screenshot request:", n);
695
+ I.current.set(E, n), U.current.set(E, K), T("Sending screenshot request:", t);
675
696
  try {
676
- c.current.send(JSON.stringify(n));
677
- } catch (_) {
678
- Y("Failed to send screenshot request immediately:", _), R.current.delete(t), I.current.delete(t), O(_);
697
+ c.current.send(JSON.stringify(t));
698
+ } catch (r) {
699
+ Y("Failed to send screenshot request immediately:", r), I.current.delete(E), U.current.delete(E), K(r);
679
700
  return;
680
701
  }
681
702
  setTimeout(() => {
682
- R.current.has(t) && (Y(`Screenshot request timed out for id ${t}`), I.current.get(t)?.(new Error("Screenshot request timed out")), R.current.delete(t), I.current.delete(t));
703
+ I.current.has(E) && (Y(`Screenshot request timed out for id ${E}`), U.current.get(E)?.(new Error("Screenshot request timed out")), I.current.delete(E), U.current.delete(E));
683
704
  }, 3e4);
684
705
  })
685
706
  })), /* @__PURE__ */ ne(
686
707
  "div",
687
708
  {
688
- className: Ye(
709
+ className: fe(
689
710
  "rc-container",
690
711
  // Use custom CSS class instead of Tailwind
691
- E
712
+ i
692
713
  ),
693
714
  style: { touchAction: "none" },
694
- onMouseDown: m,
695
- onMouseMove: m,
696
- onMouseUp: m,
697
- onMouseLeave: m,
698
- onTouchStart: m,
699
- onTouchMove: m,
700
- onTouchEnd: m,
701
- onTouchCancel: m,
715
+ onMouseDown: S,
716
+ onMouseMove: S,
717
+ onMouseUp: S,
718
+ onMouseLeave: S,
719
+ onTouchStart: S,
720
+ onTouchMove: S,
721
+ onTouchEnd: S,
722
+ onTouchCancel: S,
702
723
  children: [
703
724
  /* @__PURE__ */ q(
704
725
  "video",
@@ -721,7 +742,7 @@ const Te = De(
721
742
  }
722
743
  }
723
744
  ),
724
- !y && /* @__PURE__ */ ne("div", { className: "rc-placeholder-wrapper", children: [
745
+ !N && /* @__PURE__ */ ne("div", { className: "rc-placeholder-wrapper", children: [
725
746
  /* @__PURE__ */ q("div", { className: "rc-spinner" }),
726
747
  /* @__PURE__ */ q("p", { className: "rc-placeholder-content", children: "Connecting..." })
727
748
  ] })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@limrun/ui",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -486,7 +486,33 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
486
486
  const rtcConfig = await rtcConfigPromise;
487
487
  peerConnectionRef.current = new RTCPeerConnection(rtcConfig);
488
488
  peerConnectionRef.current.addTransceiver('audio', { direction: 'recvonly' });
489
- peerConnectionRef.current.addTransceiver('video', { direction: 'recvonly' });
489
+ const videoTransceiver = peerConnectionRef.current.addTransceiver('video', { direction: 'recvonly' });
490
+
491
+ // As hardware encoder, we use H265 for iOS and VP9 for Android.
492
+ // We make sure these two are the first ones in the list.
493
+ // If not, the fallback is H264 which is also hardware accelerated, although not as good,
494
+ // available on all platforms.
495
+ //
496
+ // The rest is not important.
497
+ if (RTCRtpReceiver.getCapabilities) {
498
+ const capabilities = RTCRtpReceiver.getCapabilities('video');
499
+ if (capabilities && capabilities.codecs) {
500
+ const codecs = capabilities.codecs;
501
+ const sortedCodecs = codecs.sort((a, b) => {
502
+ const getCodecPriority = (codec: { mimeType: string }): number => {
503
+ const mimeType = codec.mimeType.toLowerCase();
504
+ if (mimeType.includes('vp9')) return 1;
505
+ if (mimeType.includes('h265') || mimeType.includes('hevc')) return 2;
506
+ if (mimeType.includes('h264') || mimeType.includes('avc')) return 3;
507
+ return 4; // Everything else
508
+ };
509
+ return getCodecPriority(a) - getCodecPriority(b);
510
+ });
511
+ videoTransceiver.setCodecPreferences(sortedCodecs);
512
+ debugLog('Set codec preferences:', sortedCodecs.map(c => c.mimeType).join(', '));
513
+ }
514
+ }
515
+
490
516
  dataChannelRef.current = peerConnectionRef.current.createDataChannel('control', {
491
517
  ordered: true,
492
518
  negotiated: true,
@@ -497,7 +523,13 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
497
523
  updateStatus('Control channel opened');
498
524
  // Request first frame once we're ready to receive video
499
525
  if (wsRef.current) {
500
- wsRef.current.send(JSON.stringify({ type: 'requestFrame', sessionId: sessionId }));
526
+ for (let i = 0; i < 12; i++) {
527
+ setTimeout(() => {
528
+ if (wsRef.current) {
529
+ wsRef.current.send(JSON.stringify({ type: 'requestFrame', sessionId: sessionId }));
530
+ }
531
+ }, i * 125); // 125ms = quarter second
532
+ }
501
533
 
502
534
  // Send openUrl message if the prop is provided
503
535
  if (openUrl) {