diff --git a/.env.production b/.env.production index 41ec0bd8..be681c5f 100644 --- a/.env.production +++ b/.env.production @@ -13,3 +13,5 @@ VITE_AI_ENABLED=true # 统一认证登录开关 VITE_OIDC_ENABLED=false + +VITE_WEBRTC_SIGNAL_URL=wss://api.ttstd.com/ws/signal diff --git a/package.json b/package.json index de94c135..307548a6 100644 --- a/package.json +++ b/package.json @@ -131,21 +131,7 @@ }, "engines": { "node": "^24.0.0", - "pnpm": "10.34.5" - }, - "packageManager": "pnpm@10.34.5", - "pnpm": { - "overrides": { - "follow-redirects": "^1.16.2", - "source-map-js": "^1.2.2", - "brace-expansion@1": "^1.1.21", - "brace-expansion@2": "^2.1.7", - "brace-expansion@5": "^5.0.12", - "vite": "~8.2.4", - "fast-uri@3": "^3.1.8", - "@modelcontextprotocol/server@2": "~2.3.0", - "glob@10": "^10.5.0" - } + "pnpm": ">=10.34.5 <13" }, "repository": "https://gitee.com/youlaiorg/vue3-element-admin.git", "author": "有来开源组织", diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..6e22015f --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,17 @@ +verifyDepsBeforeRun: false + +allowBuilds: + '@parcel/watcher': false + core-js-pure: false + es5-ext: false + +overrides: + follow-redirects: ^1.16.2 + source-map-js: ^1.2.2 + brace-expansion@1: ^1.1.21 + brace-expansion@2: ^2.1.7 + brace-expansion@5: ^5.0.12 + vite: ~8.2.4 + fast-uri@3: ^3.1.8 + '@modelcontextprotocol/server@2': ~2.3.0 + glob@10: ^10.5.0 diff --git a/src/api/remote-control.ts b/src/api/remote-control.ts new file mode 100644 index 00000000..aabc7c6e --- /dev/null +++ b/src/api/remote-control.ts @@ -0,0 +1,18 @@ +import request from "@/utils/request"; + +export async function openRemoteControlSignal(sn: string) { + const defaultUrl = new URL( + "/ws/signal", + import.meta.env.VITE_APP_API_URL || window.location.origin, + ); + if (defaultUrl.protocol === "https:") defaultUrl.protocol = "wss:"; + const url = new URL(import.meta.env.VITE_WEBRTC_SIGNAL_URL || defaultUrl.href); + if (url.protocol !== "wss:") throw new Error("远程协助需要 WSS 信令服务器"); + const ticket = await request({ + url: "/api/v1/device/remote-control/ticket", + method: "post", + params: { sn }, + }); + url.searchParams.set("ticket", ticket); + return new WebSocket(url); +} diff --git a/src/views/devices/remote-control/controlProtocol.ts b/src/views/devices/remote-control/controlProtocol.ts new file mode 100644 index 00000000..256d2ebf --- /dev/null +++ b/src/views/devices/remote-control/controlProtocol.ts @@ -0,0 +1,72 @@ +export type TouchMode = 0 | 1 | 2; + +export function readTouchMode(data: ArrayBuffer): TouchMode { + const bytes = new Uint8Array(data); + let position = 0; + function readVarint() { + let value = 0; + for (let shift = 0; shift <= 28 && position < bytes.length; shift += 7) { + const byte = bytes[position++]; + value += (byte & 127) * 2 ** shift; + if ((byte & 128) === 0) return value; + } + throw new Error("Invalid control report"); + } + + try { + while (position < bytes.length) { + const tag = readVarint(); + switch (tag & 7) { + case 0: { + const value = readVarint(); + if (tag >> 3 === 17) return value === 1 || value === 2 ? value : 0; + break; + } + case 2: { + const length = readVarint(); + position += length; + break; + } + default: + return 0; + } + } + } catch { + return 0; + } + return 0; +} + +export type Point = { x: number; y: number }; + +export function encodeTouch(action: number, start: Point, end?: Point, detail?: number) { + const bytes = new Uint8Array(48); + const view = new DataView(bytes.buffer); + let offset = 0; + function varint(field: number, value: number) { + bytes[offset++] = field << 3; + while (value >= 128) { + bytes[offset++] = (value & 127) | 128; + value >>>= 7; + } + bytes[offset++] = value; + } + function coordinate(field: number, value: number) { + bytes[offset++] = (field << 3) | 1; + view.setFloat64(offset, value, true); + offset += 8; + } + varint(1, action); + if (action === 2 && end) { + coordinate(4, start.x); + coordinate(5, start.y); + coordinate(6, end.x); + coordinate(7, end.y); + varint(8, detail ?? 300); + } else { + coordinate(2, start.x); + coordinate(3, start.y); + if (action === 5) varint(11, detail ?? 0); + } + return bytes.slice(0, offset); +} diff --git a/src/views/devices/remote-control/index.vue b/src/views/devices/remote-control/index.vue new file mode 100644 index 00000000..d975b933 --- /dev/null +++ b/src/views/devices/remote-control/index.vue @@ -0,0 +1,308 @@ + + + + + diff --git a/types/env.d.ts b/types/env.d.ts index 60128685..f14e03f8 100644 --- a/types/env.d.ts +++ b/types/env.d.ts @@ -7,6 +7,7 @@ interface ImportMetaEnv { readonly VITE_APP_PORT: string; readonly VITE_APP_BASE_API: string; readonly VITE_APP_API_URL: string; + readonly VITE_WEBRTC_SIGNAL_URL?: string; readonly VITE_APP_TITLE?: string; readonly VITE_APP_TENANT_ENABLED?: string; readonly VITE_MOCK_DEV_SERVER: boolean;