Skip to content

Commit 1cc2978

Browse files
committed
Language and Whip changes
1 parent 3f66cc3 commit 1cc2978

11 files changed

Lines changed: 142 additions & 1459 deletions

File tree

package-lock.json

Lines changed: 2 additions & 1411 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,5 @@
9898
"target": "dmg"
9999
}
100100
},
101-
"allowScripts": {
102-
"electron@40.10.3": true,
103-
"electron-winstaller@5.4.0": true,
104-
"esbuild@0.28.0": true,
105-
"esbuild@0.27.7": true,
106-
"koffi@2.16.2": true
107-
}
101+
"allowScripts": {}
108102
}

src/components/Trackpad/ScreenMirror.tsx

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,21 @@
33
import type React from "react"
44
import { useEffect, useRef } from "react"
55

6+
import { t } from "../../utils/i18n"
7+
68
interface ScreenMirrorProps {
79
scrollMode: boolean
810
isTracking: boolean
911
handlers: React.HTMLAttributes<HTMLDivElement>
1012
videoStream: MediaStream | null
1113
trackActive: boolean
14+
status: "connecting" | "connected" | "disconnected"
1215
}
1316

1417
const TEXTS = {
15-
WAITING: "Connecting to host desktop...",
16-
AUTOMATIC: "Establishing secure connection",
18+
get AUTOMATIC() {
19+
return t("screenMirror", "establishingSecure")
20+
},
1721
}
1822

1923
export const ScreenMirror = ({
@@ -22,6 +26,7 @@ export const ScreenMirror = ({
2226
handlers,
2327
videoStream,
2428
trackActive,
29+
status,
2530
}: ScreenMirrorProps) => {
2631
const videoElementRef = useRef<HTMLVideoElement | null>(null)
2732
useEffect(() => {
@@ -32,16 +37,40 @@ export const ScreenMirror = ({
3237
video.play().catch(() => {})
3338
}
3439
return () => {
35-
video.srcObject = null
40+
if (video) {
41+
video.srcObject = null
42+
}
3643
}
3744
}, [videoStream])
3845

46+
const getWaitingText = () => {
47+
switch (status) {
48+
case "disconnected":
49+
return t("screenMirror", "disconnected")
50+
case "connected":
51+
return t("screenMirror", "connectedButNoVideo")
52+
default:
53+
return t("screenMirror", "connecting")
54+
}
55+
}
56+
57+
const getSubText = () => {
58+
switch (status) {
59+
case "disconnected":
60+
return t("screenMirror", "checkNetwork")
61+
case "connected":
62+
return t("screenMirror", "settingUpScreen")
63+
default:
64+
return TEXTS.AUTOMATIC
65+
}
66+
}
67+
3968
return (
4069
<div className="absolute inset-0 flex items-center justify-center bg-black overflow-hidden select-none touch-none">
4170
{/* Hardware Accelerated Video Renderer */}
4271
<video
4372
ref={videoElementRef}
44-
aria-label="Remote desktop screen share"
73+
aria-label={t("screenMirror", "ariaLabel")}
4574
autoPlay
4675
playsInline
4776
muted
@@ -56,8 +85,8 @@ export const ScreenMirror = ({
5685
<div className="absolute inset-0 flex flex-col items-center justify-center text-gray-400 gap-4 bg-base-300">
5786
<div className="loading loading-spinner loading-lg text-primary" />
5887
<div className="text-center px-6">
59-
<p className="font-semibold text-lg">{TEXTS.WAITING}</p>
60-
<p className="text-sm opacity-60">{TEXTS.AUTOMATIC}</p>
88+
<p className="font-semibold text-lg">{getWaitingText()}</p>
89+
<p className="text-sm opacity-60">{getSubText()}</p>
6190
</div>
6291
</div>
6392
)}

src/routes/trackpad.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ function TrackpadPage() {
4141
const isComposingRef = useRef(false)
4242
const [keyboardOpen, setKeyboardOpen] = useState(false)
4343
const [extraKeysVisible, setExtraKeysVisible] = useState(true)
44-
const { send, sendCombo } = useRemoteConnection()
44+
const { status, send, sendCombo } = useRemoteConnection()
4545
const { trackActive, videoStream } = useWebRtcStream({
4646
token,
4747
})
@@ -215,6 +215,7 @@ function TrackpadPage() {
215215
handlers={handlers}
216216
videoStream={videoStream}
217217
trackActive={trackActive}
218+
status={status}
218219
/>
219220
{bufferText !== "" && <BufferBar bufferText={bufferText} />}
220221
</div>

src/server/api/apiHandlers.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,21 @@ export async function handleWhipSignalingExchange(
672672
return
673673
}
674674

675+
let token = url.searchParams.get("token")
676+
if (!token) {
677+
const authHeader = req.headers.authorization ?? ""
678+
if (authHeader.startsWith("Bearer ")) {
679+
token = authHeader.slice(7).trim()
680+
} else if (authHeader.startsWith("Bearer_")) {
681+
token = authHeader.slice(7).trim()
682+
}
683+
}
684+
685+
if (!token || !isKnownToken(token)) {
686+
json(res, 401, { error: "Unauthorized" })
687+
return
688+
}
689+
675690
const hostOfferSdp = await readBody(req)
676691
logger.info(`WHIP offer received for session: ${sessionId}`)
677692

src/server/api/apiState.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ export type HostStatus = "stopped" | "starting" | "running" | "error"
3636
export const sessions = new Map<string, Session>()
3737
export const sseClients = new Map<string, Set<ServerResponse>>()
3838
export const inputConnections = new Map<string, InputPeerConnection>()
39-
export const WHIP_INTERNAL_PORT = 8001
4039

4140
export let hostStatus: HostStatus = "stopped"
4241
export let runnerInstance: HostRunner | null = null

src/server/gstreamer/gstManager.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,17 +81,16 @@ export class GstManager extends EventEmitter {
8181
args.push(
8282
"!",
8383
"whipclientsink",
84-
`signaller::whip-endpoint=http://localhost:${whipPort}/api/webrtc/whip?sessionId=${this.sessionId}`,
84+
`signaller::whip-endpoint=http://localhost:${whipPort}/api/webrtc/whip?sessionId=${this.sessionId}&token=${token}`,
8585
`signaller::auth-token=Bearer_${token}`,
8686
)
8787

8888
return args
8989
}
9090

91-
public async start(token: string): Promise<void> {
91+
public async start(token: string, whipPort: number): Promise<void> {
9292
if (this.process) return
9393
this.intentionalStop = false
94-
const whipPort = 8001
9594

9695
logger.info("Spawning GStreamer WHIP engine")
9796

@@ -199,7 +198,7 @@ export class GstManager extends EventEmitter {
199198
"target-bitrate=2500000",
200199
"!",
201200
"whipclientsink",
202-
`signaller::whip-endpoint=http://localhost:${serverPort}/api/webrtc/whip?sessionId=${this.sessionId}`,
201+
`signaller::whip-endpoint=http://localhost:${serverPort}/api/webrtc/whip?sessionId=${this.sessionId}&token=${token}`,
203202
`signaller::auth-token=Bearer_${token}`,
204203
]
205204

src/server/gstreamer/hostRunner.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export class HostRunner {
4545
this.activeSessions.delete(sessionId)
4646
})
4747

48-
gst.start(this.token).catch((err) => {
48+
gst.start(this.token, this.serverPort).catch((err) => {
4949
logger.error(`Failed to launch GstManager: ${String(err)}`)
5050
})
5151
}

src/server/server.ts

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@
33
*/
44

55
import type { IncomingMessage, ServerResponse } from "node:http"
6-
import http from "node:http"
6+
import { AsyncLocalStorage } from "node:async_hooks"
77
import logger from "../utils/logger"
8-
import { WHIP_INTERNAL_PORT } from "./api/apiState"
98
import {
109
handleCreateSession,
1110
handleGetSession,
@@ -85,25 +84,7 @@ const routes: Route[] = [
8584
},
8685
]
8786

88-
function startWhipInternalServer(): void {
89-
const server = http.createServer((req, res) => {
90-
const url = new URL(req.url ?? "", `http://127.0.0.1:${WHIP_INTERNAL_PORT}`)
91-
if (req.method === "POST" && url.pathname === "/api/webrtc/whip") {
92-
handleWhipSignalingExchange(req, res)
93-
} else {
94-
res.writeHead(404)
95-
res.end()
96-
}
97-
})
98-
server.listen(WHIP_INTERNAL_PORT, "127.0.0.1", () => {
99-
logger.info(
100-
`WHIP internal endpoint listening on port ${WHIP_INTERNAL_PORT}`,
101-
)
102-
})
103-
server.on("error", (err) => {
104-
logger.error(`WHIP internal server failed to start: ${String(err)}`)
105-
})
106-
}
87+
const reinStorage = new AsyncLocalStorage<boolean>()
10788

10889
export function attachSignalingRoutes(
10990
server: NonNullable<import("vite").ViteDevServer["httpServer"]>,
@@ -124,30 +105,61 @@ export function attachSignalingRoutes(
124105
const match = pathname.match(route.pattern)
125106
if (!match) continue
126107

108+
const anyRes = res as ServerResponse & { __handledByRein?: boolean }
109+
anyRes.__handledByRein = true
110+
127111
const originalSetHeader = res.setHeader.bind(res)
128112
const originalWriteHead = res.writeHead.bind(res)
129-
res.setHeader = (...args: Parameters<typeof res.setHeader>) => {
113+
const originalWrite = res.write.bind(res)
114+
const originalEnd = res.end.bind(res)
115+
116+
res.setHeader = ((...args: Parameters<typeof res.setHeader>) => {
117+
if (anyRes.__handledByRein && !reinStorage.getStore()) {
118+
return res
119+
}
130120
if (res.headersSent) return res
131121
return originalSetHeader(...args)
132-
}
122+
}) as typeof res.setHeader
123+
133124
res.writeHead = ((...args: unknown[]) => {
125+
if (anyRes.__handledByRein && !reinStorage.getStore()) {
126+
return res
127+
}
134128
if (res.writableEnded) return res
135129
return (originalWriteHead as (...a: unknown[]) => ServerResponse)(
136130
...args,
137131
)
138132
}) as typeof res.writeHead
139133

134+
res.write = ((...args: unknown[]) => {
135+
if (anyRes.__handledByRein && !reinStorage.getStore()) {
136+
return true
137+
}
138+
return (originalWrite as (...a: unknown[]) => boolean)(...args)
139+
}) as typeof res.write
140+
141+
res.end = ((...args: unknown[]) => {
142+
if (anyRes.__handledByRein && !reinStorage.getStore()) {
143+
return res
144+
}
145+
return (originalEnd as (...a: unknown[]) => ServerResponse)(...args)
146+
}) as typeof res.end
147+
140148
const params = match.slice(1)
141-
Promise.resolve(route.handler(req, res, ...params)).catch((err) => {
149+
Promise.resolve(
150+
reinStorage.run(true, () => route.handler(req, res, ...params)),
151+
).catch((err) => {
142152
logger.error(`Signaling route error: ${String(err)}`)
143-
if (!res.headersSent)
144-
json(res, 500, { error: "Internal server error" })
153+
if (!res.headersSent) {
154+
reinStorage.run(true, () => {
155+
json(res, 500, { error: "Internal server error" })
156+
})
157+
}
145158
})
146159
return
147160
}
148161
},
149162
)
150-
startWhipInternalServer()
151163
logger.info("Signaling HTTP routes attached")
152164
}
153165

src/utils/i18n.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* Lightweight internationalization resource layer.
3+
*/
4+
5+
export const i18n = {
6+
en: {
7+
screenMirror: {
8+
ariaLabel: "Remote desktop screen share",
9+
connecting: "Connecting to host...",
10+
disconnected: "Disconnected from host",
11+
connectedButNoVideo: "Establishing stream...",
12+
establishingSecure: "Establishing secure connection",
13+
settingUpScreen: "Setting up screen sharing",
14+
checkNetwork: "Please check your network or token",
15+
},
16+
},
17+
} as const
18+
19+
export type Locale = keyof typeof i18n
20+
export type TranslationKeys = typeof i18n.en
21+
22+
const currentLocale: Locale = "en"
23+
24+
/**
25+
* Basic translation helper to retrieve localized strings.
26+
*/
27+
export function t<
28+
K1 extends keyof TranslationKeys,
29+
K2 extends keyof TranslationKeys[K1],
30+
>(category: K1, key: K2): string {
31+
return (
32+
(i18n[currentLocale][category] as Record<string, string>)[
33+
key as unknown as string
34+
] ??
35+
(i18n.en[category] as Record<string, string>)[key as unknown as string] ??
36+
""
37+
)
38+
}

0 commit comments

Comments
 (0)