@limrun/ui 0.1.0 → 0.3.0
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/README.md +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +259 -244
- package/package.json +43 -43
- package/src/components/remote-control.tsx +27 -1
- package/tsconfig.json +25 -25
- package/tsconfig.node.json +25 -25
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Limrun React Components
|
|
2
2
|
|
|
3
|
-
`@
|
|
3
|
+
`@limrun/ui` contains the React components needed to embed Limrun instances in your web applications.
|
|
4
4
|
|
|
5
5
|
See [examples](../../examples/) to see how it can be used.
|
|
6
6
|
|
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"),N=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,E=0,d=0){const C=new ArrayBuffer(32),T=new DataView(C);let _=0;return T.setUint8(_,j.INJECT_TOUCH_EVENT),_+=1,T.setUint8(_,i),_+=1,T.setBigInt64(_,BigInt(u)),_+=8,T.setInt32(_,Math.round(l),!0),_+=4,T.setInt32(_,Math.round(A),!0),_+=4,T.setUint16(_,D,!0),_+=2,T.setUint16(_,s,!0),_+=2,T.setInt16(_,Math.round(o*65535),!0),_+=2,T.setInt32(_,E,!0),_+=4,T.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 E=o;return l&&(E=o!==A),E&&(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=N.forwardRef(({className:i,url:u,token:D,sessionId:s,openUrl:l},A)=>{const o=N.useRef(null),E=N.useRef(null),d=N.useRef(null),C=N.useRef(null),[T,_]=N.useState(!1),k=N.useRef(void 0),I=N.useRef(new Map),U=N.useRef(new Map),m=N.useRef(new Map),R=N.useMemo(()=>s||Math.random().toString(36).substring(2,15)+Math.random().toString(36).substring(2,15),[s]),O=t=>{g(t)},L=t=>{!C.current||C.current.readyState!=="open"||C.current.send(t)},S=t=>{if(t.preventDefault(),t.stopPropagation(),!C.current||C.current.readyState!=="open"||!o.current)return;const K=o.current,c=K.getBoundingClientRect(),r=K.videoWidth,n=K.videoHeight;if(!r||!n)return;const y=(a,f,p,M)=>{const h=c.width,v=c.height,$=r/n,se=h/v;let H=h,B=v;$>se?B=h/$:H=v*$;const ae=(h-H)/2,de=(v-B)/2,V=f-c.left-ae,G=p-c.top-de,x=V>=0&&V<=H&&G>=0&&G<=B;let J=0,q=0;x&&(J=Math.max(0,Math.min(r,V/H*r)),q=Math.max(0,Math.min(n,G/B*n)));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,r,n,P.x,P.y,ue,ee,ee);te&&L(te)}else(M==="up"||M==="cancel")&&m.current.delete(a)};if("touches"in t){const a=t.changedTouches;let f;switch(t.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(t.type){case"mousedown":t.button===0&&(f="down");break;case"mousemove":m.current.has(-1)&&(f="move");break;case"mouseup":t.button===0&&(f="up");break;case"mouseleave":m.current.has(-1)&&(f="up");break}f&&y(-1,t.clientX,t.clientY,f)}},Q=t=>{if(t.preventDefault(),t.stopPropagation(),g("Keyboard event:",{type:t.type,key:t.key,keyCode:t.keyCode,code:t.code,target:t.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(t.type==="keydown"){if(t.key.toLowerCase()==="v"&&(t.metaKey||t.ctrlKey)){g("Paste shortcut detected"),navigator.clipboard.readText().then(c=>{if(c){g("Pasting text via SET_CLIPBOARD:",c.substring(0,20)+(c.length>20?"...":""));const r=De(c,!0);L(r)}}).catch(c=>{console.error("Failed to read clipboard contents: ",c)});return}if(t.key.toLowerCase()==="m"&&(t.metaKey||t.ctrlKey)){g("Menu shortcut detected");const c=W(e.ACTION_DOWN,e.MENU,0,e.META_NONE);L(c);const r=W(e.ACTION_UP,e.MENU,0,e.META_NONE);L(r);return}}const K=Oe(t);if(K){const{keycode:c,metaState:r}=K,n=t.type==="keydown"?e.ACTION_DOWN:e.ACTION_UP;g(`Sending Keycode: key=${t.key}, code=${c}, action=${n}, meta=${r}`);const y=W(n,c,0,r);L(y)}else g(`Ignoring unhandled key event: type=${t.type}, key=${t.key}`)},oe=()=>{E.current&&E.current.readyState===WebSocket.OPEN&&E.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{E.current=new WebSocket(`${u}?token=${D}`),E.current.onerror=r=>{O("WebSocket error: "+r)},E.current.onclose=()=>{O("WebSocket closed")},await new Promise((r,n)=>{E.current&&(E.current.onopen=r,setTimeout(()=>n(new Error("WebSocket connection timeout")),5e3))});const K=await new Promise((r,n)=>{const y=setTimeout(()=>n(new Error("RTCConfiguration timeout")),5e3),a=f=>{try{const p=JSON.parse(f.data);p.type==="rtcConfiguration"&&(clearTimeout(y),E.current?.removeEventListener("message",a),r(p.rtcConfiguration))}catch(p){console.error("Error handling RTC configuration:",p),n(p)}};E.current?.addEventListener("message",a),E.current?.send(JSON.stringify({type:"requestRtcConfiguration",sessionId:R}))});d.current=new RTCPeerConnection(K),d.current.addTransceiver("audio",{direction:"recvonly"});const c=d.current.addTransceiver("video",{direction:"recvonly"});if(RTCRtpReceiver.getCapabilities){const r=RTCRtpReceiver.getCapabilities("video");if(r&&r.codecs){const y=r.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)});c.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"),E.current&&(E.current.send(JSON.stringify({type:"requestFrame",sessionId:R})),l))try{const r=decodeURIComponent(l);O("Opening URL"),E.current.send(JSON.stringify({type:"openUrl",url:r,sessionId:R}))}catch(r){console.error({error:r},"Error decoding URL, falling back to the original URL"),E.current.send(JSON.stringify({type:"openUrl",url:l,sessionId:R}))}},C.current.onclose=()=>{O("Control channel closed")},C.current.onerror=r=>{console.error("Control channel error:",r),O("Control channel error: "+r)},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=r=>{O("Received remote track: "+r.track.kind),r.track.kind==="video"&&o.current&&(g(`[${new Date().toISOString()}] Video track received:`,r.track),o.current.srcObject=r.streams[0])},d.current.onicecandidate=r=>{if(r.candidate&&E.current){const n={type:"candidate",candidate:r.candidate.candidate,sdpMid:r.candidate.sdpMid,sdpMLineIndex:r.candidate.sdpMLineIndex,sessionId:R};E.current.send(JSON.stringify(n)),O("Sent ICE candidate")}else O("ICE candidate gathering completed")},E.current.onmessage=async r=>{let n;try{n=JSON.parse(r.data)}catch(y){Y("Error parsing message:",y);return}switch(O("Received: "+n.type),n.type){case"answer":if(!d.current){O("No peer connection, skipping answer");break}await d.current.setRemoteDescription(new RTCSessionDescription({type:"answer",sdp:n.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:n.candidate,sdpMid:n.sdpMid,sdpMLineIndex:n.sdpMLineIndex})),O("Added ICE candidate");break;case"screenshot":if(typeof n.id!="string"||typeof n.dataUri!="string"){Y("Received invalid screenshot success message:",n);break}const y=I.current.get(n.id);if(!y){Y(`Received screenshot data for unknown or handled id: ${n.id}`);break}g(`Received screenshot data for id ${n.id}`),y({dataUri:n.dataUri}),I.current.delete(n.id),U.current.delete(n.id);break;case"screenshotError":if(typeof n.id!="string"||typeof n.message!="string"){Y("Received invalid screenshot error message:",n);break}const a=U.current.get(n.id);if(!a){Y(`Received screenshot error for unknown or handled id: ${n.id}`);break}Y(`Received screenshot error for id ${n.id}: ${n.message}`),a(new Error(n.message)),I.current.delete(n.id),U.current.delete(n.id);break;default:Y(`Received unhandled message type: ${n.type}`,n);break}},d.current){const r=await d.current.createOffer({offerToReceiveVideo:!0,offerToReceiveAudio:!1});await d.current.setLocalDescription(r),E.current&&E.current.send(JSON.stringify({type:"offer",sdp:r.sdp,sessionId:R})),O("Sent offer")}}catch(t){O("Error: "+t)}},Ee=()=>{E.current&&(E.current.close(),E.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")};N.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 N.useImperativeHandle(A,()=>({openUrl:t=>{if(!E.current||E.current.readyState!==WebSocket.OPEN){Y("WebSocket not open, cannot send open_url command via ref.");return}try{const K=decodeURIComponent(t);O("Opening URL"),E.current.send(JSON.stringify({type:"openUrl",url:K,sessionId:R}))}catch(K){Y("Error decoding or sending URL via ref:",{error:K,url:t}),E.current.send(JSON.stringify({type:"openUrl",url:t,sessionId:R}))}},sendKeyEvent:t=>{if(!C.current||C.current.readyState!=="open"){Y("Data channel not ready for imperative key command:",C.current?.readyState);return}const K=re[t.code];if(!K){Y(`Unknown event.code for imperative command: ${t.code}`);return}let c=e.META_NONE;t.shiftKey&&(c|=e.META_SHIFT_ON),t.altKey&&(c|=e.META_ALT_ON),t.ctrlKey&&(c|=e.META_CTRL_ON),t.metaKey&&(c|=e.META_META_ON);const r=t.type==="keydown"?e.ACTION_DOWN:e.ACTION_UP;g(`Sending Imperative Key Command: code=${t.code}, keycode=${K}, action=${r}, meta=${c}`);const n=W(r,K,0,c);n&&L(n)},screenshot:()=>new Promise((t,K)=>{if(!E.current||E.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 c=`ui-ss-${Date.now()}-${Math.random().toString(36).substring(2,9)}`,r={type:"screenshot",id:c};I.current.set(c,t),U.current.set(c,K),g("Sending screenshot request:",r);try{E.current.send(JSON.stringify(r))}catch(n){Y("Failed to send screenshot request immediately:",n),I.current.delete(c),U.current.delete(c),K(n);return}setTimeout(()=>{I.current.has(c)&&(Y(`Screenshot request timed out for id ${c}`),U.current.get(c)?.(new Error("Screenshot request timed out")),I.current.delete(c),U.current.delete(c))},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")}}),!T&&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
|
|
3
|
-
import './index.css';function re(
|
|
4
|
-
var
|
|
5
|
-
if (typeof
|
|
6
|
-
else if (typeof
|
|
7
|
-
var l =
|
|
8
|
-
for (
|
|
9
|
-
} else for (D in
|
|
10
|
-
return
|
|
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
|
|
13
|
-
for (var
|
|
14
|
-
return
|
|
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
|
-
},
|
|
20
|
+
}, b = {
|
|
21
21
|
ACTION_DOWN: 0,
|
|
22
22
|
ACTION_UP: 1,
|
|
23
23
|
ACTION_MOVE: 2,
|
|
@@ -256,449 +256,464 @@ const Q = {
|
|
|
256
256
|
NumpadEnter: e.KEYCODE_NUMPAD_ENTER,
|
|
257
257
|
NumpadEqual: e.KEYCODE_NUMPAD_EQUALS
|
|
258
258
|
};
|
|
259
|
-
function
|
|
260
|
-
const
|
|
261
|
-
let
|
|
262
|
-
return
|
|
259
|
+
function Ye(i, u, D, s, l, A, o = 1, E = 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(_, E, !0), _ += 4, N.setInt32(_, d, !0), C;
|
|
263
263
|
}
|
|
264
|
-
function Ae(
|
|
265
|
-
const
|
|
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
|
|
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(
|
|
270
|
-
const l = new ArrayBuffer(14),
|
|
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
|
|
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
|
|
275
|
-
window.debugRemoteControl && console.log(...
|
|
276
|
-
}, Y = (...
|
|
277
|
-
window.debugRemoteControl && console.warn(...
|
|
274
|
+
const T = (...i) => {
|
|
275
|
+
window.debugRemoteControl && console.log(...i);
|
|
276
|
+
}, Y = (...i) => {
|
|
277
|
+
window.debugRemoteControl && console.warn(...i);
|
|
278
278
|
};
|
|
279
|
-
function
|
|
280
|
-
const
|
|
279
|
+
function pe(i) {
|
|
280
|
+
const u = i.code, D = oe[u];
|
|
281
281
|
if (!D)
|
|
282
|
-
return Y(`Unknown event.code: ${
|
|
283
|
-
let
|
|
284
|
-
const l =
|
|
285
|
-
let
|
|
286
|
-
return l && (
|
|
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
|
+
let E = o;
|
|
286
|
+
return l && (E = o !== A), E && (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 =
|
|
289
|
-
({ className:
|
|
290
|
-
const o =
|
|
291
|
-
() =>
|
|
292
|
-
[
|
|
293
|
-
),
|
|
294
|
-
|
|
295
|
-
},
|
|
296
|
-
!
|
|
297
|
-
},
|
|
298
|
-
if (
|
|
288
|
+
const Te = Ce(
|
|
289
|
+
({ className: i, url: u, token: D, sessionId: s, openUrl: l }, A) => {
|
|
290
|
+
const o = P(null), E = 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 = (t) => {
|
|
294
|
+
T(t);
|
|
295
|
+
}, L = (t) => {
|
|
296
|
+
!C.current || C.current.readyState !== "open" || C.current.send(t);
|
|
297
|
+
}, S = (t) => {
|
|
298
|
+
if (t.preventDefault(), t.stopPropagation(), !C.current || C.current.readyState !== "open" || !o.current)
|
|
299
299
|
return;
|
|
300
|
-
const
|
|
301
|
-
if (!
|
|
302
|
-
const
|
|
303
|
-
const
|
|
304
|
-
let v =
|
|
305
|
-
W > ae ? H =
|
|
306
|
-
const de = (
|
|
300
|
+
const K = o.current, c = K.getBoundingClientRect(), r = K.videoWidth, n = K.videoHeight;
|
|
301
|
+
if (!r || !n) return;
|
|
302
|
+
const p = (a, f, y, g) => {
|
|
303
|
+
const M = c.width, F = c.height, W = r / n, 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 - c.left - de, V = y - c.top - ue, G = $ >= 0 && $ <= v && V >= 0 && V <= H;
|
|
307
307
|
let J = 0, x = 0;
|
|
308
|
-
G && (J = Math.max(0, Math.min(
|
|
309
|
-
let
|
|
310
|
-
const ee =
|
|
311
|
-
switch (
|
|
308
|
+
G && (J = Math.max(0, Math.min(r, $ / v * r)), x = Math.max(0, Math.min(n, V / H * n)));
|
|
309
|
+
let w = null, R = null, _e = 1;
|
|
310
|
+
const ee = b.BUTTON_PRIMARY;
|
|
311
|
+
switch (g) {
|
|
312
312
|
case "down":
|
|
313
|
-
G ? (
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
324
|
-
const te =
|
|
325
|
-
|
|
326
|
-
|
|
323
|
+
if (w !== null && R !== null) {
|
|
324
|
+
const te = Ye(
|
|
325
|
+
w,
|
|
326
|
+
a,
|
|
327
|
+
r,
|
|
327
328
|
n,
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
h.y,
|
|
329
|
+
R.x,
|
|
330
|
+
R.y,
|
|
331
331
|
_e,
|
|
332
332
|
ee,
|
|
333
333
|
ee
|
|
334
334
|
);
|
|
335
|
-
te &&
|
|
336
|
-
} else (
|
|
335
|
+
te && L(te);
|
|
336
|
+
} else (g === "up" || g === "cancel") && m.current.delete(a);
|
|
337
337
|
};
|
|
338
|
-
if ("touches" in
|
|
339
|
-
const
|
|
340
|
-
let
|
|
341
|
-
switch (
|
|
338
|
+
if ("touches" in t) {
|
|
339
|
+
const a = t.changedTouches;
|
|
340
|
+
let f;
|
|
341
|
+
switch (t.type) {
|
|
342
342
|
case "touchstart":
|
|
343
|
-
|
|
343
|
+
f = "down";
|
|
344
344
|
break;
|
|
345
345
|
case "touchmove":
|
|
346
|
-
|
|
346
|
+
f = "move";
|
|
347
347
|
break;
|
|
348
348
|
case "touchend":
|
|
349
|
-
|
|
349
|
+
f = "up";
|
|
350
350
|
break;
|
|
351
351
|
case "touchcancel":
|
|
352
|
-
|
|
352
|
+
f = "cancel";
|
|
353
353
|
break;
|
|
354
354
|
default:
|
|
355
355
|
return;
|
|
356
356
|
}
|
|
357
|
-
for (let
|
|
358
|
-
const
|
|
359
|
-
|
|
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
|
|
363
|
-
switch (
|
|
362
|
+
let f = null;
|
|
363
|
+
switch (t.type) {
|
|
364
364
|
case "mousedown":
|
|
365
|
-
|
|
365
|
+
t.button === 0 && (f = "down");
|
|
366
366
|
break;
|
|
367
367
|
case "mousemove":
|
|
368
|
-
|
|
368
|
+
m.current.has(-1) && (f = "move");
|
|
369
369
|
break;
|
|
370
370
|
case "mouseup":
|
|
371
|
-
|
|
371
|
+
t.button === 0 && (f = "up");
|
|
372
372
|
break;
|
|
373
373
|
case "mouseleave":
|
|
374
|
-
|
|
374
|
+
m.current.has(-1) && (f = "up");
|
|
375
375
|
break;
|
|
376
376
|
}
|
|
377
|
-
|
|
377
|
+
f && p(-1, t.clientX, t.clientY, f);
|
|
378
378
|
}
|
|
379
|
-
}, X = (
|
|
380
|
-
if (
|
|
381
|
-
type:
|
|
382
|
-
key:
|
|
383
|
-
keyCode:
|
|
384
|
-
code:
|
|
385
|
-
target:
|
|
379
|
+
}, X = (t) => {
|
|
380
|
+
if (t.preventDefault(), t.stopPropagation(), T("Keyboard event:", {
|
|
381
|
+
type: t.type,
|
|
382
|
+
key: t.key,
|
|
383
|
+
keyCode: t.keyCode,
|
|
384
|
+
code: t.code,
|
|
385
|
+
target: t.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 (!
|
|
392
|
-
Y("Data channel not ready for keyboard event:",
|
|
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 (
|
|
396
|
-
if (
|
|
397
|
-
|
|
398
|
-
if (
|
|
399
|
-
|
|
395
|
+
if (t.type === "keydown") {
|
|
396
|
+
if (t.key.toLowerCase() === "v" && (t.metaKey || t.ctrlKey)) {
|
|
397
|
+
T("Paste shortcut detected"), navigator.clipboard.readText().then((c) => {
|
|
398
|
+
if (c) {
|
|
399
|
+
T(
|
|
400
400
|
"Pasting text via SET_CLIPBOARD:",
|
|
401
|
-
|
|
401
|
+
c.substring(0, 20) + (c.length > 20 ? "..." : "")
|
|
402
402
|
);
|
|
403
|
-
const
|
|
404
|
-
|
|
403
|
+
const r = Ae(c, !0);
|
|
404
|
+
L(r);
|
|
405
405
|
}
|
|
406
|
-
}).catch((
|
|
407
|
-
console.error("Failed to read clipboard contents: ",
|
|
406
|
+
}).catch((c) => {
|
|
407
|
+
console.error("Failed to read clipboard contents: ", c);
|
|
408
408
|
});
|
|
409
409
|
return;
|
|
410
410
|
}
|
|
411
|
-
if (
|
|
412
|
-
|
|
413
|
-
const
|
|
411
|
+
if (t.key.toLowerCase() === "m" && (t.metaKey || t.ctrlKey)) {
|
|
412
|
+
T("Menu shortcut detected");
|
|
413
|
+
const c = 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
|
-
|
|
421
|
-
const
|
|
420
|
+
L(c);
|
|
421
|
+
const r = B(
|
|
422
422
|
e.ACTION_UP,
|
|
423
423
|
e.MENU,
|
|
424
424
|
0,
|
|
425
425
|
e.META_NONE
|
|
426
426
|
);
|
|
427
|
-
|
|
427
|
+
L(r);
|
|
428
428
|
return;
|
|
429
429
|
}
|
|
430
430
|
}
|
|
431
|
-
const
|
|
432
|
-
if (
|
|
433
|
-
const { keycode:
|
|
434
|
-
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
431
|
+
const K = pe(t);
|
|
432
|
+
if (K) {
|
|
433
|
+
const { keycode: c, metaState: r } = K, n = t.type === "keydown" ? e.ACTION_DOWN : e.ACTION_UP;
|
|
434
|
+
T(`Sending Keycode: key=${t.key}, code=${c}, action=${n}, meta=${r}`);
|
|
435
|
+
const p = B(
|
|
436
|
+
n,
|
|
437
|
+
c,
|
|
438
438
|
0,
|
|
439
439
|
// repeat count, typically 0 for single presses
|
|
440
|
-
|
|
440
|
+
r
|
|
441
441
|
);
|
|
442
|
-
|
|
442
|
+
L(p);
|
|
443
443
|
} else
|
|
444
|
-
|
|
444
|
+
T(`Ignoring unhandled key event: type=${t.type}, key=${t.key}`);
|
|
445
445
|
}, ce = () => {
|
|
446
|
-
|
|
446
|
+
E.current && E.current.readyState === WebSocket.OPEN && E.current.send(
|
|
447
447
|
JSON.stringify({
|
|
448
448
|
type: "keepAlive",
|
|
449
|
-
sessionId:
|
|
449
|
+
sessionId: h
|
|
450
450
|
})
|
|
451
451
|
);
|
|
452
452
|
}, j = () => {
|
|
453
|
-
|
|
453
|
+
k.current && window.clearInterval(k.current), k.current = window.setInterval(ce, 1e4);
|
|
454
454
|
}, Z = () => {
|
|
455
|
-
|
|
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
|
-
|
|
461
|
-
|
|
462
|
-
},
|
|
463
|
-
|
|
464
|
-
}, await new Promise((
|
|
465
|
-
|
|
460
|
+
E.current = new WebSocket(`${u}?token=${D}`), E.current.onerror = (r) => {
|
|
461
|
+
O("WebSocket error: " + r);
|
|
462
|
+
}, E.current.onclose = () => {
|
|
463
|
+
O("WebSocket closed");
|
|
464
|
+
}, await new Promise((r, n) => {
|
|
465
|
+
E.current && (E.current.onopen = r, setTimeout(() => n(new Error("WebSocket connection timeout")), 5e3));
|
|
466
466
|
});
|
|
467
|
-
const
|
|
468
|
-
const
|
|
467
|
+
const K = await new Promise((r, n) => {
|
|
468
|
+
const p = setTimeout(() => n(new Error("RTCConfiguration timeout")), 5e3), a = (f) => {
|
|
469
469
|
try {
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
} catch (
|
|
473
|
-
console.error("Error handling RTC configuration:",
|
|
470
|
+
const y = JSON.parse(f.data);
|
|
471
|
+
y.type === "rtcConfiguration" && (clearTimeout(p), E.current?.removeEventListener("message", a), r(y.rtcConfiguration));
|
|
472
|
+
} catch (y) {
|
|
473
|
+
console.error("Error handling RTC configuration:", y), n(y);
|
|
474
474
|
}
|
|
475
475
|
};
|
|
476
|
-
|
|
476
|
+
E.current?.addEventListener("message", a), E.current?.send(
|
|
477
477
|
JSON.stringify({
|
|
478
478
|
type: "requestRtcConfiguration",
|
|
479
|
-
sessionId:
|
|
479
|
+
sessionId: h
|
|
480
480
|
})
|
|
481
481
|
);
|
|
482
482
|
});
|
|
483
|
-
|
|
483
|
+
d.current = new RTCPeerConnection(K), d.current.addTransceiver("audio", { direction: "recvonly" });
|
|
484
|
+
const c = d.current.addTransceiver("video", { direction: "recvonly" });
|
|
485
|
+
if (RTCRtpReceiver.getCapabilities) {
|
|
486
|
+
const r = RTCRtpReceiver.getCapabilities("video");
|
|
487
|
+
if (r && r.codecs) {
|
|
488
|
+
const p = r.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
|
+
c.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
|
-
}),
|
|
488
|
-
if (
|
|
502
|
+
}), C.current.onopen = () => {
|
|
503
|
+
if (O("Control channel opened"), E.current && (E.current.send(JSON.stringify({ type: "requestFrame", sessionId: h })), l))
|
|
489
504
|
try {
|
|
490
|
-
const
|
|
491
|
-
|
|
505
|
+
const r = decodeURIComponent(l);
|
|
506
|
+
O("Opening URL"), E.current.send(
|
|
492
507
|
JSON.stringify({
|
|
493
508
|
type: "openUrl",
|
|
494
|
-
url:
|
|
495
|
-
sessionId:
|
|
509
|
+
url: r,
|
|
510
|
+
sessionId: h
|
|
496
511
|
})
|
|
497
512
|
);
|
|
498
|
-
} catch (
|
|
499
|
-
console.error({ error:
|
|
513
|
+
} catch (r) {
|
|
514
|
+
console.error({ error: r }, "Error decoding URL, falling back to the original URL"), E.current.send(
|
|
500
515
|
JSON.stringify({
|
|
501
516
|
type: "openUrl",
|
|
502
517
|
url: l,
|
|
503
|
-
sessionId:
|
|
518
|
+
sessionId: h
|
|
504
519
|
})
|
|
505
520
|
);
|
|
506
521
|
}
|
|
507
|
-
},
|
|
508
|
-
|
|
509
|
-
},
|
|
510
|
-
console.error("Control channel error:",
|
|
511
|
-
},
|
|
512
|
-
|
|
513
|
-
},
|
|
514
|
-
|
|
515
|
-
},
|
|
516
|
-
|
|
517
|
-
},
|
|
518
|
-
if (
|
|
522
|
+
}, C.current.onclose = () => {
|
|
523
|
+
O("Control channel closed");
|
|
524
|
+
}, C.current.onerror = (r) => {
|
|
525
|
+
console.error("Control channel error:", r), O("Control channel error: " + r);
|
|
526
|
+
}, d.current.onconnectionstatechange = () => {
|
|
527
|
+
O("Connection state: " + d.current?.connectionState), _(d.current?.connectionState === "connected");
|
|
528
|
+
}, d.current.oniceconnectionstatechange = () => {
|
|
529
|
+
O("ICE state: " + d.current?.iceConnectionState);
|
|
530
|
+
}, d.current.ontrack = (r) => {
|
|
531
|
+
O("Received remote track: " + r.track.kind), r.track.kind === "video" && o.current && (T(`[${(/* @__PURE__ */ new Date()).toISOString()}] Video track received:`, r.track), o.current.srcObject = r.streams[0]);
|
|
532
|
+
}, d.current.onicecandidate = (r) => {
|
|
533
|
+
if (r.candidate && E.current) {
|
|
519
534
|
const n = {
|
|
520
535
|
type: "candidate",
|
|
521
|
-
candidate:
|
|
522
|
-
sdpMid:
|
|
523
|
-
sdpMLineIndex:
|
|
524
|
-
sessionId:
|
|
536
|
+
candidate: r.candidate.candidate,
|
|
537
|
+
sdpMid: r.candidate.sdpMid,
|
|
538
|
+
sdpMLineIndex: r.candidate.sdpMLineIndex,
|
|
539
|
+
sessionId: h
|
|
525
540
|
};
|
|
526
|
-
|
|
541
|
+
E.current.send(JSON.stringify(n)), O("Sent ICE candidate");
|
|
527
542
|
} else
|
|
528
|
-
|
|
529
|
-
},
|
|
543
|
+
O("ICE candidate gathering completed");
|
|
544
|
+
}, E.current.onmessage = async (r) => {
|
|
530
545
|
let n;
|
|
531
546
|
try {
|
|
532
|
-
n = JSON.parse(
|
|
533
|
-
} catch (
|
|
534
|
-
Y("Error parsing message:",
|
|
547
|
+
n = JSON.parse(r.data);
|
|
548
|
+
} catch (p) {
|
|
549
|
+
Y("Error parsing message:", p);
|
|
535
550
|
return;
|
|
536
551
|
}
|
|
537
|
-
switch (
|
|
552
|
+
switch (O("Received: " + n.type), n.type) {
|
|
538
553
|
case "answer":
|
|
539
|
-
if (!
|
|
540
|
-
|
|
554
|
+
if (!d.current) {
|
|
555
|
+
O("No peer connection, skipping answer");
|
|
541
556
|
break;
|
|
542
557
|
}
|
|
543
|
-
await
|
|
558
|
+
await d.current.setRemoteDescription(
|
|
544
559
|
new RTCSessionDescription({
|
|
545
560
|
type: "answer",
|
|
546
561
|
sdp: n.sdp
|
|
547
562
|
})
|
|
548
|
-
),
|
|
563
|
+
), O("Set remote description");
|
|
549
564
|
break;
|
|
550
565
|
case "candidate":
|
|
551
|
-
if (!
|
|
552
|
-
|
|
566
|
+
if (!d.current) {
|
|
567
|
+
O("No peer connection, skipping candidate");
|
|
553
568
|
break;
|
|
554
569
|
}
|
|
555
|
-
await
|
|
570
|
+
await d.current.addIceCandidate(
|
|
556
571
|
new RTCIceCandidate({
|
|
557
572
|
candidate: n.candidate,
|
|
558
573
|
sdpMid: n.sdpMid,
|
|
559
574
|
sdpMLineIndex: n.sdpMLineIndex
|
|
560
575
|
})
|
|
561
|
-
),
|
|
576
|
+
), O("Added ICE candidate");
|
|
562
577
|
break;
|
|
563
578
|
case "screenshot":
|
|
564
579
|
if (typeof n.id != "string" || typeof n.dataUri != "string") {
|
|
565
580
|
Y("Received invalid screenshot success message:", n);
|
|
566
581
|
break;
|
|
567
582
|
}
|
|
568
|
-
const
|
|
569
|
-
if (!
|
|
583
|
+
const p = I.current.get(n.id);
|
|
584
|
+
if (!p) {
|
|
570
585
|
Y(`Received screenshot data for unknown or handled id: ${n.id}`);
|
|
571
586
|
break;
|
|
572
587
|
}
|
|
573
|
-
|
|
588
|
+
T(`Received screenshot data for id ${n.id}`), p({ dataUri: n.dataUri }), I.current.delete(n.id), U.current.delete(n.id);
|
|
574
589
|
break;
|
|
575
590
|
case "screenshotError":
|
|
576
591
|
if (typeof n.id != "string" || typeof n.message != "string") {
|
|
577
592
|
Y("Received invalid screenshot error message:", n);
|
|
578
593
|
break;
|
|
579
594
|
}
|
|
580
|
-
const
|
|
581
|
-
if (!
|
|
595
|
+
const a = U.current.get(n.id);
|
|
596
|
+
if (!a) {
|
|
582
597
|
Y(`Received screenshot error for unknown or handled id: ${n.id}`);
|
|
583
598
|
break;
|
|
584
599
|
}
|
|
585
|
-
Y(`Received screenshot error for id ${n.id}: ${n.message}`),
|
|
600
|
+
Y(`Received screenshot error for id ${n.id}: ${n.message}`), a(new Error(n.message)), I.current.delete(n.id), U.current.delete(n.id);
|
|
586
601
|
break;
|
|
587
602
|
default:
|
|
588
603
|
Y(`Received unhandled message type: ${n.type}`, n);
|
|
589
604
|
break;
|
|
590
605
|
}
|
|
591
|
-
},
|
|
592
|
-
const
|
|
606
|
+
}, d.current) {
|
|
607
|
+
const r = await d.current.createOffer({
|
|
593
608
|
offerToReceiveVideo: !0,
|
|
594
609
|
offerToReceiveAudio: !1
|
|
595
610
|
});
|
|
596
|
-
await
|
|
611
|
+
await d.current.setLocalDescription(r), E.current && E.current.send(
|
|
597
612
|
JSON.stringify({
|
|
598
613
|
type: "offer",
|
|
599
|
-
sdp:
|
|
600
|
-
sessionId:
|
|
614
|
+
sdp: r.sdp,
|
|
615
|
+
sessionId: h
|
|
601
616
|
})
|
|
602
|
-
),
|
|
617
|
+
), O("Sent offer");
|
|
603
618
|
}
|
|
604
|
-
} catch (
|
|
605
|
-
|
|
619
|
+
} catch (t) {
|
|
620
|
+
O("Error: " + t);
|
|
606
621
|
}
|
|
607
622
|
}, ie = () => {
|
|
608
|
-
|
|
623
|
+
E.current && (E.current.close(), E.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
624
|
};
|
|
610
625
|
Ke(() => (Ee(), document.hidden || j(), document.addEventListener("visibilitychange", z), () => {
|
|
611
626
|
Z(), ie(), document.removeEventListener("visibilitychange", z);
|
|
612
|
-
}), [
|
|
627
|
+
}), [u, D, s]);
|
|
613
628
|
const se = () => {
|
|
614
629
|
o.current && o.current.focus();
|
|
615
630
|
};
|
|
616
|
-
return le(
|
|
617
|
-
openUrl: (
|
|
618
|
-
if (!
|
|
631
|
+
return le(A, () => ({
|
|
632
|
+
openUrl: (t) => {
|
|
633
|
+
if (!E.current || E.current.readyState !== WebSocket.OPEN) {
|
|
619
634
|
Y("WebSocket not open, cannot send open_url command via ref.");
|
|
620
635
|
return;
|
|
621
636
|
}
|
|
622
637
|
try {
|
|
623
|
-
const
|
|
624
|
-
|
|
638
|
+
const K = decodeURIComponent(t);
|
|
639
|
+
O("Opening URL"), E.current.send(
|
|
625
640
|
JSON.stringify({
|
|
626
641
|
type: "openUrl",
|
|
627
|
-
url:
|
|
628
|
-
sessionId:
|
|
642
|
+
url: K,
|
|
643
|
+
sessionId: h
|
|
629
644
|
})
|
|
630
645
|
);
|
|
631
|
-
} catch (
|
|
632
|
-
Y("Error decoding or sending URL via ref:", { error:
|
|
646
|
+
} catch (K) {
|
|
647
|
+
Y("Error decoding or sending URL via ref:", { error: K, url: t }), E.current.send(
|
|
633
648
|
JSON.stringify({
|
|
634
649
|
type: "openUrl",
|
|
635
|
-
url:
|
|
636
|
-
sessionId:
|
|
650
|
+
url: t,
|
|
651
|
+
sessionId: h
|
|
637
652
|
})
|
|
638
653
|
);
|
|
639
654
|
}
|
|
640
655
|
},
|
|
641
|
-
sendKeyEvent: (
|
|
642
|
-
if (!
|
|
643
|
-
Y("Data channel not ready for imperative key command:",
|
|
656
|
+
sendKeyEvent: (t) => {
|
|
657
|
+
if (!C.current || C.current.readyState !== "open") {
|
|
658
|
+
Y("Data channel not ready for imperative key command:", C.current?.readyState);
|
|
644
659
|
return;
|
|
645
660
|
}
|
|
646
|
-
const
|
|
647
|
-
if (!
|
|
648
|
-
Y(`Unknown event.code for imperative command: ${
|
|
661
|
+
const K = oe[t.code];
|
|
662
|
+
if (!K) {
|
|
663
|
+
Y(`Unknown event.code for imperative command: ${t.code}`);
|
|
649
664
|
return;
|
|
650
665
|
}
|
|
651
|
-
let
|
|
652
|
-
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
`Sending Imperative Key Command: code=${
|
|
666
|
+
let c = e.META_NONE;
|
|
667
|
+
t.shiftKey && (c |= e.META_SHIFT_ON), t.altKey && (c |= e.META_ALT_ON), t.ctrlKey && (c |= e.META_CTRL_ON), t.metaKey && (c |= e.META_META_ON);
|
|
668
|
+
const r = t.type === "keydown" ? e.ACTION_DOWN : e.ACTION_UP;
|
|
669
|
+
T(
|
|
670
|
+
`Sending Imperative Key Command: code=${t.code}, keycode=${K}, action=${r}, meta=${c}`
|
|
656
671
|
);
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
|
|
672
|
+
const n = B(
|
|
673
|
+
r,
|
|
674
|
+
K,
|
|
660
675
|
0,
|
|
661
676
|
// repeat count, typically 0 for single presses
|
|
662
|
-
|
|
677
|
+
c
|
|
663
678
|
);
|
|
664
|
-
|
|
679
|
+
n && L(n);
|
|
665
680
|
},
|
|
666
|
-
screenshot: () => new Promise((
|
|
667
|
-
if (!
|
|
668
|
-
return Y("WebSocket not open, cannot send screenshot command."),
|
|
669
|
-
const
|
|
681
|
+
screenshot: () => new Promise((t, K) => {
|
|
682
|
+
if (!E.current || E.current.readyState !== WebSocket.OPEN)
|
|
683
|
+
return Y("WebSocket not open, cannot send screenshot command."), K(new Error("WebSocket is not connected or connection is not open."));
|
|
684
|
+
const c = `ui-ss-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, r = {
|
|
670
685
|
type: "screenshot",
|
|
671
686
|
// Matches the type expected by instance API
|
|
672
|
-
id:
|
|
687
|
+
id: c
|
|
673
688
|
};
|
|
674
|
-
|
|
689
|
+
I.current.set(c, t), U.current.set(c, K), T("Sending screenshot request:", r);
|
|
675
690
|
try {
|
|
676
|
-
|
|
677
|
-
} catch (
|
|
678
|
-
Y("Failed to send screenshot request immediately:",
|
|
691
|
+
E.current.send(JSON.stringify(r));
|
|
692
|
+
} catch (n) {
|
|
693
|
+
Y("Failed to send screenshot request immediately:", n), I.current.delete(c), U.current.delete(c), K(n);
|
|
679
694
|
return;
|
|
680
695
|
}
|
|
681
696
|
setTimeout(() => {
|
|
682
|
-
|
|
697
|
+
I.current.has(c) && (Y(`Screenshot request timed out for id ${c}`), U.current.get(c)?.(new Error("Screenshot request timed out")), I.current.delete(c), U.current.delete(c));
|
|
683
698
|
}, 3e4);
|
|
684
699
|
})
|
|
685
700
|
})), /* @__PURE__ */ ne(
|
|
686
701
|
"div",
|
|
687
702
|
{
|
|
688
|
-
className:
|
|
703
|
+
className: fe(
|
|
689
704
|
"rc-container",
|
|
690
705
|
// Use custom CSS class instead of Tailwind
|
|
691
|
-
|
|
706
|
+
i
|
|
692
707
|
),
|
|
693
708
|
style: { touchAction: "none" },
|
|
694
|
-
onMouseDown:
|
|
695
|
-
onMouseMove:
|
|
696
|
-
onMouseUp:
|
|
697
|
-
onMouseLeave:
|
|
698
|
-
onTouchStart:
|
|
699
|
-
onTouchMove:
|
|
700
|
-
onTouchEnd:
|
|
701
|
-
onTouchCancel:
|
|
709
|
+
onMouseDown: S,
|
|
710
|
+
onMouseMove: S,
|
|
711
|
+
onMouseUp: S,
|
|
712
|
+
onMouseLeave: S,
|
|
713
|
+
onTouchStart: S,
|
|
714
|
+
onTouchMove: S,
|
|
715
|
+
onTouchEnd: S,
|
|
716
|
+
onTouchCancel: S,
|
|
702
717
|
children: [
|
|
703
718
|
/* @__PURE__ */ q(
|
|
704
719
|
"video",
|
|
@@ -721,7 +736,7 @@ const Te = De(
|
|
|
721
736
|
}
|
|
722
737
|
}
|
|
723
738
|
),
|
|
724
|
-
!
|
|
739
|
+
!N && /* @__PURE__ */ ne("div", { className: "rc-placeholder-wrapper", children: [
|
|
725
740
|
/* @__PURE__ */ q("div", { className: "rc-spinner" }),
|
|
726
741
|
/* @__PURE__ */ q("p", { className: "rc-placeholder-content", children: "Connecting..." })
|
|
727
742
|
] })
|
package/package.json
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
},
|
|
7
|
-
"main": "./dist/index.js",
|
|
8
|
-
"module": "./dist/index.js",
|
|
9
|
-
"types": "./dist/index.d.ts",
|
|
10
|
-
"exports": {
|
|
11
|
-
".": {
|
|
12
|
-
"types": "./dist/index.d.ts",
|
|
13
|
-
"import": "./dist/index.js",
|
|
14
|
-
"require": "./dist/index.cjs"
|
|
2
|
+
"name": "@limrun/ui",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
15
6
|
},
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
},
|
|
16
|
+
"./package.json": "./package.json"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"dev": "vite",
|
|
20
|
+
"build": "tsc && vite build",
|
|
21
|
+
"prepublishOnly": "npm run build",
|
|
22
|
+
"lint": "eslint .",
|
|
23
|
+
"preview": "vite preview"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/limrun-inc/typescript-sdk",
|
|
28
|
+
"directory": "packages/ui"
|
|
29
|
+
},
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/react": "^19.1.12",
|
|
33
|
+
"@types/react-dom": "^19.1.9",
|
|
34
|
+
"@vitejs/plugin-react-swc": "^4.0.1",
|
|
35
|
+
"path": "^0.12.7",
|
|
36
|
+
"react": "^19.1.1",
|
|
37
|
+
"react-dom": "^19.1.1",
|
|
38
|
+
"vite": "^7.1.4",
|
|
39
|
+
"vite-plugin-dts": "^4.5.4",
|
|
40
|
+
"vite-plugin-lib-inject-css": "^2.2.2"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"clsx": "^2.1.1"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -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,
|
package/tsconfig.json
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"allowImportingTsExtensions": false,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"noEmit": false,
|
|
13
|
+
"emitDeclarationOnly": true,
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"declarationDir": "dist",
|
|
16
|
+
"jsx": "react-jsx",
|
|
17
|
+
"strict": true,
|
|
18
|
+
"noUnusedLocals": true,
|
|
19
|
+
"noUnusedParameters": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"outDir": "dist"
|
|
22
|
+
},
|
|
23
|
+
"include": ["src"],
|
|
24
|
+
"exclude": ["**/*.test.ts", "**/*.test.tsx", "node_modules", "dist"],
|
|
25
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
26
|
+
}
|
package/tsconfig.node.json
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"composite": true,
|
|
4
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
5
|
+
"target": "ES2022",
|
|
6
|
+
"lib": ["ES2023"],
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
|
|
10
|
+
/* Bundler mode */
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"moduleDetection": "force",
|
|
15
|
+
"noEmit": false,
|
|
16
|
+
"emitDeclarationOnly": true,
|
|
17
|
+
|
|
18
|
+
/* Linting */
|
|
19
|
+
"strict": true,
|
|
20
|
+
"noUnusedLocals": true,
|
|
21
|
+
"noUnusedParameters": true,
|
|
22
|
+
"noFallthroughCasesInSwitch": true,
|
|
23
|
+
"noUncheckedSideEffectImports": true
|
|
24
|
+
},
|
|
25
|
+
"include": ["vite.config.ts"]
|
|
26
|
+
}
|